Compare commits

...

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

@ -24,18 +24,11 @@ import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
//该工具类用于通过电话号码查询联系人姓名,并且会对查询结果进行缓存,从而避免重复查询,提高后续查询的效率。
public class Contact {
// 用于缓存已查询过的电话号码及其对应的联系人姓名
private static HashMap<String, String> sContactCache;
private static final String TAG = "Contact";
// 用于查询联系人的 SQL 条件语句,借助 PHONE_NUMBERS_EQUAL 函数
// 来匹配电话号码,同时会对原始联系人 ID 进行验证
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
@ -43,55 +36,36 @@ public class Contact {
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
//依据给定的电话号码,在联系人数据库中查找对应的联系人姓名。 要是找到了联系人,就会将结果缓存起来,方便后续使用。上下文对象,用于获取 ContentResolver。要查询的电话号码。若找到联系人返回其显示名称若未找到或者出现错误返回 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);
}
// 针对当前电话号码,定制化查询条件
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 执行联系人数据库查询
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI, // 查询的内容 URI
new String [] { Phone.DISPLAY_NAME }, // 要获取的字段
selection, // 定制后的查询条件
new String[] { phoneNumber }, // 查询参数
null); // 不使用排序
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
// 处理查询结果
if (cursor != null && cursor.moveToFirst()) {
try {
// 获取联系人姓名并缓存
String name = cursor.getString(0);
sContactCache.put(phoneNumber, name);
return name;
} catch (IndexOutOfBoundsException e) {
// 处理可能出现的索引越界异常
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
// 确保游标资源被关闭
cursor.close();
}
} else {
// 未找到匹配的联系人
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}

@ -1,37 +0,0 @@
*.iml
.DS_Store
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Intellij
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/dictionaries
.idea/libraries
.idea/
# Keystore files
*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

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

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

@ -1,20 +0,0 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "25.0.1"
defaultConfig {
applicationId "net.micode.notes"
minSdkVersion 14
targetSdkVersion 14
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
android{ useLibrary 'org.apache.http.legacy' }
}

@ -1,150 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.micode.notes"
android:versionCode="1"
android:versionName="0.1" >
<uses-sdk android:minSdkVersion="14" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/icon_app"
android:label="@string/app_name" >
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/NoteTheme" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<provider
android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" />
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_4x_info" />
</receiver>
<receiver android:name=".ui.AlarmInitReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
</receiver>
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
</service>
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
</application>
</manifest>

@ -1,244 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.net.Uri;
/**
*
* URI
*/
public class Notes {
public static final String AUTHORITY = "micode_notes"; // 内容提供器的 authority用于ContentProvider标识
public static final String TAG = "Notes"; // 日志标签
// 数据类型常量
public static final int TYPE_NOTE = 0;// 数据类型常量
public static final int TYPE_FOLDER = 1; // 文件夹类型
public static final int TYPE_SYSTEM = 2;// 系统类型(如系统文件夹)
/**
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
// Intent传递的额外参数键名
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";// 背景颜色ID
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 桌面小部件ID
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";// 小部件类型
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期
// 桌面小部件类型常量
public static final int TYPE_WIDGET_INVALIDE = -1;// 无效类型
public static final int TYPE_WIDGET_2X = 0;// 2x尺寸小部件
public static final int TYPE_WIDGET_4X = 1;// 4x尺寸小部件
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记MIME类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;// 通话笔记MIME类型
}
/**
*
* MIME
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); // 通用数据查询URI包含所有笔记和文件夹
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); // 具体数据项查询URI如附件、内容详情
public interface NoteColumns {
public static final String ID = "_id";
public static final String PARENT_ID = "parent_id";//父级ID笔记所属文件夹ID文件夹的父文件夹ID
public static final String CREATED_DATE = "created_date";//创建时间(时间戳)
public static final String MODIFIED_DATE = "modified_date";//最后修改时间(时间戳)
public static final String ALERTED_DATE = "alert_date";//提醒时间(时间戳)
public static final String SNIPPET = "snippet";//文件夹名称或笔记内容摘要
public static final String WIDGET_ID = "widget_id";//关联的桌面小部件ID
public static final String WIDGET_TYPE = "widget_type";//桌面小部件类型
public static final String BG_COLOR_ID = "bg_color_id";//背景颜色ID
/**
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
*/
public static final String NOTES_COUNT = "notes_count";
/**
*/
public static final String TYPE = "type";
/**
ID
*/
public static final String SYNC_ID = "sync_id";
/**
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
ID
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
GoogleID
*/
public static final String GTASK_ID = "gtask_id";
/**
*/
public static final String VERSION = "version";
}
/**
*
*
*/
public interface DataColumns {
public static final String ID = "_id"; // 行唯一标识
/**
MIME
*/
public static final String MIME_TYPE = "mime_type";
/**
IDNoteID
*/
public static final String NOTE_ID = "note_id";
/**
*/
public static final String CREATED_DATE = "created_date";
/**
*/
public static final String MODIFIED_DATE = "modified_date";
/**
MIME
*/
public static final String CONTENT = "content";
/**
1MIME
*/
public static final String DATA1 = "data1";
/**
2MIME
*/
public static final String DATA2 = "data2";
/**
3MIME
*/
public static final String DATA3 = "data3";
/**
4MIME
*/
public static final String DATA4 = "data4";
/**
5MIME
*/
public static final String DATA5 = "data5";
}
public static final class TextNote implements DataColumns {
/**
1=0=
*/
public static final String MODE = DATA1;
public static final int MODE_CHECK_LIST = 1;// 待办清单模式标识
/**
*
*/
public static final String BOLD = DATA2;
// MIME类型定义
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 文本笔记内容URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
public static final class CallNote implements DataColumns {
/**
*/
public static final String CALL_DATE = DATA1;
/**
*/
public static final String PHONE_NUMBER = DATA3;
// MIME类型定义
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 通话笔记内容URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -1,374 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称
private static final String DB_NAME = "note.db";
// 数据库版本号
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;
// 创建笔记表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID默认0
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒时间默认0
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID默认0
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间(默认当前时间戳)
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件默认0
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间(默认当前时间戳)
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 子笔记数量默认0
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 内容摘要(默认空)
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 类型默认0笔记
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID默认0
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型(默认-1无效
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID默认0
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标记默认0
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父ID默认0
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google任务ID默认空
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号默认0
")";
// 创建数据项表的SQL语句
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型必填
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 所属笔记ID默认0
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间(默认当前时间戳)
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间(默认当前时间戳)
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 内容(默认空)
DataColumns.DATA1 + " INTEGER," + // 通用数据1整数
DataColumns.DATA2 + " INTEGER," + // 通用数据2整数
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 通用数据3文本
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 通用数据4文本
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 通用数据5文本
")";
// 创建数据项表的笔记ID索引SQL
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; // 基于note_id创建索引
/**
*
*/
// 更新笔记父文件夹时增加目标文件夹笔记数的触发器
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_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
// 插入新笔记时增加目标文件夹笔记数的触发器
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
// 删除笔记时减少原文件夹笔记数的触发器
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
// 插入文本笔记数据时更新笔记内容的触发器
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
// 更新文本笔记数据时更新笔记内容的触发器
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
// 删除文本笔记数据时清空笔记内容的触发器
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
// 删除笔记时级联删除相关数据项的触发器
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END";
// 删除文件夹时级联删除子笔记的触发器
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
// 文件夹移动到回收站时级联移动子笔记的触发器
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
// 构造方法
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 创建笔记表(含触发器和系统文件夹)
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行建表语句
reCreateNoteTableTriggers(db); // 重建触发器
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created"); // 日志记录
}
// 重建笔记表相关触发器(先删除旧触发器再创建新触发器)
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除旧触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 创建新触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
// 创建系统文件夹(根文件夹、临时文件夹等)
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
// 创建通话记录文件夹
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 创建根文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 创建临时文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 创建回收站文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
// 创建数据项表(含触发器和索引)
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行建表语句
reCreateDataTableTriggers(db); // 重建触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建索引
Log.d(TAG, "data table has been created"); // 日志记录
}
// 重建数据项表相关触发器
private void reCreateDataTableTriggers(SQLiteDatabase db) {
// 删除旧触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
// 创建新触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
// 获取单例实例
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
// 数据库创建回调(首次创建时调用)
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据项表
}
// 数据库升级回调(版本号变更时调用)
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 是否需要重建触发器
boolean skipV2 = false; // 是否跳过V2升级步骤
// 从版本1升级到版本2全量重建表
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // 标记已完成V2升级避免重复处理
oldVersion++;
}
// 从版本2升级到版本3添加GTASK_ID和回收站文件夹
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true; // 需要重建触发器
oldVersion++;
}
// 从版本3升级到版本4添加VERSION字段
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 按需重建触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 升级失败处理
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
// 升级到版本2全量重建表
private void upgradeToV2(SQLiteDatabase db) {
// 删除旧表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
// 重新创建表(包含最新结构和触发器)
createNoteTable(db);
createDataTable(db);
}
// 升级到版本3添加GTASK_ID字段和回收站文件夹
private void upgradeToV3(SQLiteDatabase db) {
// 删除不再使用的触发器(旧版本遗留)
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");
// 向笔记表添加GTASK_ID字段用于关联Google任务
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// 创建回收站系统文件夹ID为-3
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添加VERSION字段用于数据版本管理
private void upgradeToV4(SQLiteDatabase db) {
// 向笔记表添加VERSION字段记录数据版本号用于升级校验
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -1,339 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
/**
* Android ContentProvider
*
*/
public class NotesProvider extends ContentProvider {
// URI匹配器用于解析不同的URI请求
private static final UriMatcher mMatcher;
// 数据库帮助类实例(用于操作数据库)
private NotesDatabaseHelper mHelper;
// 日志标签
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
// 静态代码块初始化URI匹配器
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 匹配笔记列表URIcontent://micode_notes/note
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
// 匹配单个笔记URIcontent://micode_notes/note/123
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
// 匹配数据项列表URIcontent://micode_notes/data
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
// 匹配单个数据项URIcontent://micode_notes/data/456
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
// 匹配搜索URIcontent://micode_notes/search
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
// 匹配搜索建议URI带查询词content://micode_notes/suggest_query/xxx
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'
* - SearchManager
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 传递笔记ID到意图
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 搜索建议第一行文本
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 搜索建议第二行文本
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," // 搜索结果图标资源ID
+ "'" + 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; // 仅查询普通笔记类型
// ContentProvider初始化方法创建数据库帮助类实例
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助类单例
return true;
}
// 查询数据方法处理不同URI的查询请求
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库
String id = null; // 用于存储URI中的ID部分
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 查询笔记列表
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_NOTE_ITEM:
// 查询单个笔记从URI路径中提取ID
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection,
NoteColumns.ID + "=" + id + parseSelection(selection), // 添加ID条件和附加查询条件
selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
// 查询数据项列表(如附件)
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_DATA_ITEM:
// 查询单个数据项从URI路径中提取ID
id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection,
DataColumns.ID + "=" + id + parseSelection(selection), // 添加ID条件和附加查询条件
selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
// 处理搜索和搜索建议请求
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"搜索查询不支持排序或投影参数");
}
String searchString = null;
// 从URI中提取搜索词根据不同的URI类型
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
searchString = uri.getQueryParameter("pattern");
}
if (TextUtils.isEmpty(searchString)) {
return null; // 空搜索词返回空结果
}
try {
// 构造模糊查询参数(前后添加通配符)
searchString = String.format("%%%s%%", searchString);
// 执行原生SQL查询使用预定义的搜索语句
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString });
} catch (IllegalStateException ex) {
Log.e(TAG, "搜索查询异常: " + ex.toString());
}
break;
default:
throw new IllegalArgumentException("未知URI: " + uri);
}
if (c != null) {
// 设置内容变更通知(当数据变化时通知观察者)
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
// 插入数据方法处理不同URI的插入请求
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
long dataId = 0, noteId = 0, insertedId = 0; // 插入记录的ID
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 插入新笔记
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
// 插入新数据项需关联笔记ID
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID); // 从参数中获取所属笔记ID
} else {
Log.d(TAG, "错误数据项未包含笔记ID: " + values.toString());
}
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
throw new IllegalArgumentException("未知URI: " + uri);
}
// 通知内容变更(根据插入的是笔记还是数据项)
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); // 通知笔记URI变更
}
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); // 通知数据项URI变更
}
return ContentUris.withAppendedId(uri, insertedId); // 返回插入记录的URI
}
// 删除数据方法处理不同URI的删除请求
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
boolean deleteData = false; // 是否删除的是数据项(用于通知逻辑)
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 删除笔记列表添加条件ID>0排除系统文件夹
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 删除单个笔记从URI中提取ID排除系统文件夹
id = uri.getPathSegments().get(1);
long noteId = Long.valueOf(id);
if (noteId <= 0) { // 系统文件夹ID为负数或0不允许删除
break;
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
case URI_DATA_ITEM:
// 删除数据项(标记为删除数据项类型)
count = db.delete(TABLE.DATA,
(uri.getPathSegments().size() > 1 ? DataColumns.ID + "=" + uri.getPathSegments().get(1) : selection)
+ parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
throw new IllegalArgumentException("未知URI: " + uri);
}
if (count > 0) {
// 通知内容变更(数据项删除时需通知笔记列表,因为可能影响笔记内容)
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变更
}
return count;
}
// 更新数据方法处理不同URI的更新请求
@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; // 是否更新的是数据项(用于通知逻辑)
switch (mMatcher.match(uri)) {
case URI_NOTE:
case URI_NOTE_ITEM:
// 更新笔记时增加版本号(用于数据同步或乐观锁)
increaseNoteVersion(uri.getPathSegments().size() > 1 ?
Long.valueOf(uri.getPathSegments().get(1)) : -1, selection, selectionArgs);
// 执行更新操作
count = db.update(TABLE.NOTE, values,
(uri.getPathSegments().size() > 1 ? NoteColumns.ID + "=" + uri.getPathSegments().get(1) : selection)
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
case URI_DATA_ITEM:
// 更新数据项(标记为更新数据项类型)
count = db.update(TABLE.DATA, values,
(uri.getPathSegments().size() > 1 ? DataColumns.ID + "=" + uri.getPathSegments().get(1) : selection)
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
throw new IllegalArgumentException("未知URI: " + uri);
}
if (count > 0) {
// 通知内容变更(数据项更新时需通知笔记列表,因为可能影响笔记内容)
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变更
}
return count;
}
// 解析附加查询条件在主条件后添加AND子句
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
// 增加笔记版本号(用于数据变更跟踪,支持单条记录或批量更新)
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE " + TABLE.NOTE + " SET " + NoteColumns.VERSION + "=" + NoteColumns.VERSION + "+1 ");
// 构建WHERE条件支持按ID或自定义条件更新
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
if (!TextUtils.isEmpty(selection)) {
// 替换占位符?为实际参数值(简化处理,仅适用于简单场景)
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
sql.append(selectString);
}
}
mHelper.getWritableDatabase().execSQL(sql.toString()); // 执行SQL语句
}
// 获取数据类型未实现可根据需求返回MIME类型
@Override
public String getType(Uri uri) {
// TODO: 根据URI返回对应的MIME类型如vnd.android.cursor.dir/note
return null;
}
}

@ -1,122 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* TaskGoogle
* GoogleGTaskGID
*/
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName(); // 日志标签(类名)
// 关联的Google任务GID全局唯一标识符
private String mRelatedGid = null;
/**
*
* @param gid GoogleGID
* @param metaInfo JSON
*/
public void setMeta(String gid, JSONObject metaInfo) {
try {
// 在元数据中添加GTASK_ID字段值为传入的gid
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "向元数据中添加关联GID失败");
}
// 将元数据JSON转为字符串存储到笔记内容中
setNotes(metaInfo.toString());
// 设置任务名称为固定值(元数据任务的标识)
setName(GTaskStringUtils.META_NOTE_NAME);
}
/**
* GoogleGID
* @return GIDnull
*/
public String getRelatedGid() {
return mRelatedGid;
}
/**
* null
* @return true-false-
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null; // 只要存在元数据内容即保存
}
/**
* JSON
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js); // 调用父类方法处理公共字段
if (getNotes() != null) {
try {
// 解析存储的元数据JSON字符串
JSONObject metaInfo = new JSONObject(getNotes().trim());
// 提取关联的GID
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "从元数据中获取关联GID失败");
mRelatedGid = null; // 解析失败时清空GID
}
}
}
/**
* JSON
* @throws IllegalAccessError
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
throw new IllegalAccessError("MetaData:setContentByLocalJSON 方法不应该被调用");
}
/**
* JSON
* @return
* @throws IllegalAccessError
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent 方法不应该被调用");
}
/**
*
* @param c 使
* @return
* @throws IllegalAccessError
*/
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction 方法不应该被调用");
}
}

@ -1,103 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
/**
*
*
*/
public abstract class Node {
// 同步操作类型常量(用于标识不同的同步动作)
public static final int SYNC_ACTION_NONE = 0; // 无操作
public static final int SYNC_ACTION_ADD_REMOTE = 1; // 远程添加
public static final int SYNC_ACTION_ADD_LOCAL = 2; // 本地添加
public static final int SYNC_ACTION_DEL_REMOTE = 3; // 远程删除
public static final int SYNC_ACTION_DEL_LOCAL = 4; // 本地删除
public static final int SYNC_ACTION_UPDATE_REMOTE = 5; // 远程更新
public static final int SYNC_ACTION_UPDATE_LOCAL = 6; // 本地更新
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; // 更新冲突
public static final int SYNC_ACTION_ERROR = 8; // 错误状态
// 公共属性(所有子类共享的同步相关信息)
private String mGid; // Google任务全局唯一标识符GID
private String mName; // 节点名称(如任务标题)
private long mLastModified; // 最后修改时间戳
private boolean mDeleted; // 是否已删除标记
// 构造方法:初始化默认值
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
// 抽象方法获取创建操作的JSON由子类实现具体逻辑
public abstract JSONObject getCreateAction(int actionId);
// 抽象方法获取更新操作的JSON由子类实现具体逻辑
public abstract JSONObject getUpdateAction(int actionId);
// 抽象方法从远程JSON设置内容由子类实现解析逻辑
public abstract void setContentByRemoteJSON(JSONObject js);
// 抽象方法从本地JSON设置内容由子类实现序列化逻辑
public abstract void setContentByLocalJSON(JSONObject js);
// 抽象方法从内容获取本地JSON由子类实现反序列化逻辑
public abstract JSONObject getLocalJSONFromContent();
// 抽象方法:获取同步动作类型(由子类根据数据库游标判断)
public abstract int getSyncAction(Cursor c);
// ==================== 公共属性的访问器和修改器 ====================
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) {
this.mName = name;
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -1,222 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
/**
*
* JSON
*/
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; // 数据ID
private String mDataMimeType; // MIME类型
private String mDataContent; // 内容
private long mDataContentData1; // 附加数据1
private String mDataContentData3; // 附加数据3
private ContentValues mDiffDataValues; // 差异值,用于更新操作
/**
*
* @param context
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
/**
*
* @param context
* @param c
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
/**
*
* @param c
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/**
* JSON
* @param js JSON
* @throws JSONException JSON
*/
public void setContent(JSONObject js) throws JSONException {
// 从JSON中获取各字段值并与当前值比较记录差异
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
/**
* JSON
* @return JSON
* @throws JSONException JSON
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "数据尚未在数据库中创建");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
/**
*
* @param noteId ID
* @param validateVersion
* @param version
*/
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
// 新建数据项
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID); // 移除无效ID
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 设置所属笔记ID
// 插入数据
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1)); // 获取插入后的ID
} catch (NumberFormatException e) {
Log.e(TAG, "获取数据ID失败: " + e.toString());
throw new ActionFailureException("创建笔记失败");
}
} else {
// 更新现有数据项
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
// 不验证版本,直接更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
// 验证版本,确保在版本匹配时才更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "更新失败,可能在同步时有其他更新操作");
}
}
}
mDiffDataValues.clear(); // 清空差异值
mIsCreate = false; // 标记为已创建
}
/**
* ID
* @return ID
*/
public long getId() {
return mDataId;
}
}

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

@ -1,393 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* NodeGoogle Tasks
*
*/
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName();
private boolean mCompleted; // 任务是否已完成
private String mNotes; // 任务备注信息
private JSONObject mMetaInfo; // 任务元数据,存储与本地笔记的关联信息
private Task mPriorSibling; // 前一个兄弟任务(用于排序)
private TaskList mParent; // 父任务列表
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 设置操作类型为创建任务
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务在列表中的索引位置
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// 设置任务实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// 设置父任务列表ID
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// 设置目标父类型为任务组
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 设置任务列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// 设置前一个兄弟任务ID如果有
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("生成创建任务的JSON对象失败");
}
return js;
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 设置操作类型为更新任务
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 设置要更新的任务实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("生成更新任务的JSON对象失败");
}
return js;
}
/**
* JSON
* @param js JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// 从JSON中提取并设置任务ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 提取并设置最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 提取并设置任务名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// 提取并设置任务备注
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// 提取并设置任务是否已删除
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// 提取并设置任务是否已完成
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("从JSON对象获取任务内容失败");
}
}
}
/**
* 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: 没有可用数据");
}
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, "无效的笔记类型");
return;
}
// 从数据中提取任务名称
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT));
break;
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
/**
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
// 从网页创建的新任务
if (name == null) {
Log.w(TAG, "该笔记似乎为空");
return null;
}
// 创建新的JSON对象表示任务
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
// 已同步的任务,更新现有元数据
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 更新任务名称
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());
break;
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
/**
*
* @param metaData
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
}
}
/**
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) {
Log.w(TAG, "笔记元数据似乎已被删除");
return SYNC_ACTION_UPDATE_REMOTE;
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "远程笔记ID似乎已被删除");
return SYNC_ACTION_UPDATE_LOCAL;
}
// 验证笔记ID
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "笔记ID不匹配");
return SYNC_ACTION_UPDATE_LOCAL;
}
// 判断是否有本地修改
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 本地没有更新
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 两边都没有更新
return SYNC_ACTION_NONE;
} else {
// 应用远程更新到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// 有本地修改验证GTask ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "GTask ID不匹配");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 只有本地修改
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// 本地和远程都有修改,发生冲突
return SYNC_ACTION_UPDATE_CONFLICT;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
/**
*
* @return truefalse
*/
public boolean isWorthSaving() {
return mMetaInfo != null ||
(getName() != null && getName().trim().length() > 0) ||
(getNotes() != null && getNotes().trim().length() > 0);
}
// Getter和Setter方法
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
public void setNotes(String notes) {
this.mNotes = notes;
}
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
public void setParent(TaskList parent) {
this.mParent = parent;
}
public boolean getCompleted() {
return this.mCompleted;
}
public String getNotes() {
return this.mNotes;
}
public Task getPriorSibling() {
return this.mPriorSibling;
}
public TaskList getParent() {
return this.mParent;
}
}

@ -1,320 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
/**
* NodeGoogle Tasks
*
*/
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<>(); // 初始化子任务集合
mIndex = 1; // 默认索引为1可能用于排序
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 设置操作类型为"创建"
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务列表的排序索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// 设置任务列表实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID固定值
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 实体类型为"任务组"
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 实体变更数据
} catch (JSONException e) {
Log.e(TAG, "生成创建任务列表JSON失败: " + e.getMessage());
throw new ActionFailureException("创建任务列表JSON生成失败");
}
return js;
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 设置操作类型为"更新"
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务列表IDGID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 设置更新的实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 名称
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 删除状态
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 实体变更数据
} catch (JSONException e) {
Log.e(TAG, "生成更新任务列表JSON失败: " + e.getMessage());
throw new ActionFailureException("更新任务列表JSON生成失败");
}
return js;
}
/**
* JSON
* @param js JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// 提取并设置任务列表IDGID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 提取并设置最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 提取并设置任务列表名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
} catch (JSONException e) {
Log.e(TAG, "解析远程任务列表JSON失败: " + e.getMessage());
throw new ActionFailureException("远程任务列表内容解析失败");
}
}
}
/**
* JSON
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "本地JSON数据为空或缺少笔记元数据");
return;
}
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 提取笔记元数据
int type = folder.getInt(NoteColumns.TYPE); // 获取笔记类型
if (type == Notes.TYPE_FOLDER) {
// 普通文件夹设置名称添加MIUI前缀
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
} else if (type == Notes.TYPE_SYSTEM) {
// 系统文件夹根据ID设置固定名称
long folderId = folder.getLong(NoteColumns.ID);
if (folderId == Notes.ID_ROOT_FOLDER) {
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
} else if (folderId == Notes.ID_CALL_RECORD_FOLDER) {
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE);
} else {
Log.e(TAG, "无效的系统文件夹ID: " + folderId);
}
} else {
Log.e(TAG, "无效的笔记类型: " + type);
}
} catch (JSONException e) {
Log.e(TAG, "解析本地任务列表JSON失败: " + e.getMessage());
}
}
/**
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
String folderName = getName();
// 移除MIUI前缀还原本地文件夹名称
if (folderName.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) {
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length());
}
// 设置文件夹类型
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) ||
folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) {
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 系统文件夹
} else {
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 普通文件夹
}
folder.put(NoteColumns.SNIPPET, folderName); // 设置文件夹名称
js.put(GTaskStringUtils.META_HEAD_NOTE, folder); // 封装笔记元数据
return js;
} catch (JSONException e) {
Log.e(TAG, "生成本地任务列表JSON失败: " + e.getMessage());
return null;
}
}
/**
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 本地无修改
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
return SYNC_ACTION_NONE; // 本地与远程一致,无操作
} else {
return SYNC_ACTION_UPDATE_LOCAL; // 应用远程更新到本地
}
} else {
// 本地有修改
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "GTask ID不匹配同步错误");
return SYNC_ACTION_ERROR; // GID不一致同步错误
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
return SYNC_ACTION_UPDATE_REMOTE; // 仅本地修改,更新远程
} else {
// 文件夹冲突处理策略:强制应用本地修改(可能丢失远程数据)
Log.w(TAG, "文件夹同步冲突,应用本地修改");
return SYNC_ACTION_UPDATE_REMOTE;
}
}
} catch (Exception e) {
Log.e(TAG, "同步操作判断失败: " + e.getMessage());
return SYNC_ACTION_ERROR;
}
}
// ==================== 子任务管理方法 ====================
public int getChildTaskCount() {
return mChildren.size(); // 获取子任务数量
}
public boolean addChildTask(Task task) {
if (task == null || mChildren.contains(task)) return false;
boolean ret = mChildren.add(task); // 添加子任务
if (ret) {
// 设置子任务的前一个兄弟任务和父列表
task.setPriorSibling(mChildren.size() > 1 ? mChildren.get(mChildren.size() - 2) : null);
task.setParent(this);
}
return ret;
}
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "添加子任务:无效索引");
return false;
}
if (task == null || mChildren.indexOf(task) != -1) return false;
mChildren.add(index, task); // 插入到指定索引
// 更新相邻任务的兄弟关系
Task preTask = index > 0 ? mChildren.get(index - 1) : null;
Task nextTask = index < mChildren.size() - 1 ? mChildren.get(index + 1) : null;
task.setPriorSibling(preTask);
if (nextTask != null) nextTask.setPriorSibling(task);
return true;
}
public boolean removeChildTask(Task task) {
int index = mChildren.indexOf(task);
if (index == -1) return false;
boolean ret = mChildren.remove(task); // 移除子任务
if (ret) {
task.setPriorSibling(null); // 清空被移除任务的兄弟关系
task.setParent(null); // 清空父列表引用
// 更新后续任务的前一个兄弟任务
if (index < mChildren.size()) {
Task nextTask = mChildren.get(index);
nextTask.setPriorSibling(index > 0 ? mChildren.get(index - 1) : null);
}
}
return ret;
}
public boolean moveChildTask(Task task, int newIndex) {
int oldIndex = mChildren.indexOf(task);
if (oldIndex == -1 || newIndex < 0 || newIndex >= mChildren.size()) {
Log.e(TAG, "移动子任务:无效索引或任务不存在");
return false;
}
if (oldIndex == newIndex) return true;
// 先移除再插入实现移动
return removeChildTask(task) && addChildTask(task, newIndex);
}
public Task findChildTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid)) {
return task; // 根据GID查找子任务
}
}
return null;
}
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task); // 获取子任务在列表中的索引
}
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "获取子任务:无效索引");
return null;
}
return mChildren.get(index); // 根据索引获取子任务
}
public ArrayList<Task> getChildTaskList() {
return mChildren; // 获取所有子任务列表
}
// ==================== 索引管理 ====================
public void setIndex(int index) {
this.mIndex = index; // 设置任务列表的排序索引
}
public int getIndex() {
return mIndex; // 获取排序索引
}
}

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

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

@ -1,163 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* Google Tasks
* Google Tasks
*/
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
private static final int GTASK_SYNC_NOTIFICATION_ID = 5234235; // 同步通知的唯一标识
// 完成监听器接口,用于同步完成后回调
public interface OnCompleteListener {
void onComplete(); // 同步完成回调方法
}
private Context mContext; // 上下文
private NotificationManager mNotifiManager; // 通知管理器
private GTaskManager mTaskManager; // Google Tasks管理类实例
private OnCompleteListener mOnCompleteListener; // 完成监听器
/**
*
* @param context
* @param listener
*/
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
// 获取通知管理器服务
mNotifiManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
// 获取Google Tasks管理类的单例实例
mTaskManager = GTaskManager.getInstance();
}
/**
*
*/
public void cancelSync() {
mTaskManager.cancelSync(); // 调用GTaskManager的取消同步方法
}
/**
* AsyncTask
* @param message
*/
public void publishProgess(String message) {
publishProgress(new String[]{message}); // 调用父类方法发布进度
}
/**
*
* @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 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);
}
// 设置通知内容已弃用的setLatestEventInfo改用contentIntent
notification.contentIntent = pendingIntent;
// 显示通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
/**
*
* @param unused 使
* @return GTaskManagerSTATE_*
*/
@Override
protected Integer doInBackground(Void... unused) {
// 发布登录进度消息(显示正在使用的同步账号)
publishProgess(mContext.getString(R.string.sync_progress_login,
NotesPreferenceActivity.getSyncAccountName(mContext)));
// 执行同步操作并返回结果
return mTaskManager.sync(mContext, this);
}
/**
* 线UI
* @param progress
*/
@Override
protected void onProgressUpdate(String... progress) {
showNotification(R.string.ticker_syncing, progress[0]); // 显示同步中的通知
// 如果上下文是GTaskSyncService发送广播通知进度
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
/**
* 线
* @param result
*/
@Override
protected void onPostExecute(Integer result) {
// 根据不同的结果状态显示对应的通知
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success,
mContext.getString(R.string.success_sync_account, mTaskManager.getSyncAccount()));
// 记录最后同步时间
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext.getString(R.string.error_sync_cancelled));
}
// 在子线程中执行完成回调,避免阻塞主线程
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -1,640 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* Google TasksGoogle Tasks
* /HTTP
*/
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName();
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"; // GET请求URL获取数据
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // POST请求URL提交操作
private static GTaskClient mInstance = null; // 单例实例
private DefaultHttpClient mHttpClient; // HTTP客户端
private String mGetUrl; // 当前GET请求URL支持自定义域名
private String mPostUrl; // 当前POST请求URL支持自定义域名
private long mClientVersion; // 客户端版本号由Google Tasks返回
private boolean mLoggedin; // 登录状态
private long mLastLoginTime; // 最后登录时间(用于超时控制)
private int mActionId; // 操作ID生成器保证每次请求唯一
private Account mAccount; // 当前同步的Google账号
private JSONArray mUpdateArray; // 批量更新操作队列
/**
*
*/
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
/**
*
* @return GTaskClient
*/
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
/**
* Google Tasks
* @param activity
* @return
*/
public boolean login(Activity activity) {
// 登录状态超时控制5分钟
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// 账号切换后强制重新登录
if (mLoggedin && !TextUtils.equals(getSyncAccount().name,
NotesPreferenceActivity.getSyncAccountName(activity))) {
mLoggedin = false;
}
if (mLoggedin) {
Log.d(TAG, "已登录");
return true;
}
mLastLoginTime = System.currentTimeMillis();
String authToken = loginGoogleAccount(activity, false); // 获取授权令牌
if (authToken == null) {
Log.e(TAG, "Google账号登录失败");
return false;
}
// 处理自定义域名账号非gmail/googlemail后缀
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") ||
mAccount.name.toLowerCase().endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
// 尝试使用自定义域名登录
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 通用域名登录(处理失败或默认情况)
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true;
return true;
}
/**
* Google
* @param activity
* @param invalidateToken
* @return
*/
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有Google账号
if (accounts.length == 0) {
Log.e(TAG, "无可用Google账号");
return null;
}
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
// 查找配置的同步账号
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
break;
}
}
if (account == null) {
Log.e(TAG, "无法找到配置的同步账号");
return null;
}
mAccount = account;
// 获取授权令牌
AccountManagerFuture<Bundle> future = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle bundle = future.getResult();
authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
if (invalidateToken) {
// 失效旧令牌并重新获取(用于处理令牌过期)
accountManager.invalidateAuthToken("com.google", authToken);
return loginGoogleAccount(activity, false);
}
return authToken;
} catch (Exception e) {
Log.e(TAG, "获取授权令牌失败: " + e.getMessage());
return null;
}
}
/**
* Google Tasks
* @param activity
* @param authToken
* @return
*/
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) { // 首次登录尝试
// 令牌可能过期,失效后重试
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "重新获取令牌失败");
return false;
}
if (!loginGtask(authToken)) { // 二次登录尝试
Log.e(TAG, "二次登录失败");
return false;
}
}
return true;
}
/**
* Google Tasks
* @param authToken
* @return
*/
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000; // 连接超时10秒
int timeoutSocket = 15000; // 套接字超时15秒
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore cookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(cookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
try {
String loginUrl = mGetUrl + "?auth=" + authToken; // 构造登录URL
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = mHttpClient.execute(httpGet); // 发送GET请求
// 检查Cookie是否包含GTL认证信息
List<Cookie> cookies = cookieStore.getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
break;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "未获取到认证Cookie");
}
// 解析响应获取客户端版本号
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v"); // 保存客户端版本号
return true;
} catch (JSONException e) {
Log.e(TAG, "解析登录响应失败: " + e.getMessage());
return false;
} catch (Exception e) {
Log.e(TAG, "登录请求失败: " + e.getMessage());
return false;
}
}
/**
* ID
* @return ID
*/
private int getActionId() {
return mActionId++;
}
/**
* POST
* @return HttpPost
*/
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1"); // Google Tasks特定请求头
return httpPost;
}
/**
* HTTP
* @param entity HTTP
* @return
* @throws IOException IO
*/
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "响应编码: " + contentEncoding);
}
InputStream input = entity.getContent();
// 处理GZip压缩
if ("gzip".equalsIgnoreCase(contentEncoding)) {
input = new GZIPInputStream(input);
// 处理Deflate压缩
} else if ("deflate".equalsIgnoreCase(contentEncoding)) {
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(input, inflater);
}
try {
InputStreamReader isr = new InputStreamReader(input);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
return sb.toString();
} finally {
input.close();
}
}
/**
* POST
* @param js JSON
* @return JSON
* @throws NetworkFailureException
*/
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "未登录,无法发送请求");
throw new ActionFailureException("未登录");
}
HttpPost httpPost = createHttpPost();
try {
// 构造请求参数将JSON包裹在"r"参数中)
LinkedList<BasicNameValuePair> params = new LinkedList<>();
params.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
httpPost.setEntity(entity);
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
} catch (ClientProtocolException e) {
Log.e(TAG, "协议异常: " + e.getMessage());
throw new NetworkFailureException("POST请求失败");
} catch (IOException e) {
Log.e(TAG, "IO异常: " + e.getMessage());
throw new NetworkFailureException("POST请求失败");
} catch (JSONException e) {
Log.e(TAG, "JSON解析异常: " + e.getMessage());
throw new ActionFailureException("响应解析失败");
} catch (Exception e) {
Log.e(TAG, "未知异常: " + e.getMessage());
throw new ActionFailureException("请求处理失败");
}
}
// ==================== 任务操作 ====================
/**
*
* @param task
* @throws NetworkFailureException
*/
public void createTask(Task task) throws NetworkFailureException {
commitUpdate(); // 提交之前的批量操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 添加创建任务操作到请求
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 客户端版本号
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置新生成的任务ID
} catch (JSONException e) {
Log.e(TAG, "创建任务JSON处理失败: " + e.getMessage());
throw new ActionFailureException("创建任务失败");
}
}
/**
*
* @param tasklist
* @throws NetworkFailureException
*/
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置列表ID
} catch (JSONException e) {
Log.e(TAG, "创建任务列表JSON处理失败: " + e.getMessage());
throw new ActionFailureException("创建任务列表失败");
}
}
/**
*
* @throws NetworkFailureException
*/
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
try {
JSONObject jsPost = new JSONObject();
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 发送批量更新请求
mUpdateArray = null; // 清空操作队列
} catch (JSONException e) {
Log.e(TAG, "提交批量更新失败: " + e.getMessage());
throw new ActionFailureException("批量更新失败");
}
}
}
/**
*
* @param node
* @throws NetworkFailureException
*/
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// 限制批量操作数量超过10个则先提交当前队列
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
if (mUpdateArray == null) {
mUpdateArray = new JSONArray();
}
mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加更新操作到队列
}
}
/**
*
* @param task
* @param preParent
* @param curParent
* @throws NetworkFailureException
*/
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate(); // 提交之前的批量操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// 构造移动操作请求
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 任务ID
// 同一列表内移动且非首个任务时设置前置兄弟任务ID
if (preParent == curParent && task.getPriorSibling() != null) {
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 源列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 目标父列表ID
// 跨列表移动时设置目标列表ID
if (preParent != curParent) {
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 发送移动请求
} catch (JSONException e) {
Log.e(TAG, "移动任务JSON处理失败: " + e.getMessage());
throw new ActionFailureException("移动任务失败");
}
}
/**
*
* @param node
* @throws NetworkFailureException
*/
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate(); // 提交之前的批量操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 标记节点为已删除并添加更新操作
node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 发送删除请求
mUpdateArray = null; // 清空操作队列
} catch (JSONException e) {
Log.e(TAG, "删除节点JSON处理失败: " + e.getMessage());
throw new ActionFailureException("删除节点失败");
}
}
// ==================== 查询操作 ====================
/**
*
* @return JSON
* @throws NetworkFailureException
*/
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "未登录,无法获取任务列表");
throw new ActionFailureException("未登录");
}
try {
HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = mHttpClient.execute(httpGet); // 发送GET请求
// 解析响应获取任务列表数据
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
Log.e(TAG, "获取任务列表协议异常: " + e.getMessage());
throw new NetworkFailureException("获取任务列表失败");
} catch (IOException e) {
Log.e(TAG, "获取任务列表IO异常: " + e.getMessage());
throw new NetworkFailureException("获取任务列表失败");
} catch (JSONException e) {
Log.e(TAG, "解析任务列表JSON异常: " + e.getMessage());
throw new ActionFailureException("解析任务列表失败");
}
}
/**
*
* @param listGid ID
* @return JSON
* @throws NetworkFailureException
*/
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate(); // 提交之前的批量操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// 构造获取任务列表操作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 列表ID
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 不获取已删除任务
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) {
Log.e(TAG, "获取任务JSON处理失败: " + e.getMessage());
throw new ActionFailureException("获取任务列表失败");
}
}
// ==================== 辅助方法 ====================
/**
* Google
* @return
*/
public Account getSyncAccount() {
return mAccount;
}
/**
*
*/
public void resetUpdateArray() {
mUpdateArray = null;
}
}

@ -1,848 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* Google
* Google Tasks
*/
public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName();
// 同步状态常量
public static final int STATE_SUCCESS = 0; // 同步成功
public static final int STATE_NETWORK_ERROR = 1; // 网络错误
public static final int STATE_INTERNAL_ERROR = 2; // 内部错误
public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中
public static final int STATE_SYNC_CANCELLED = 4; // 同步已取消
private static GTaskManager mInstance = null; // 单例实例
private Activity mActivity; // 活动上下文
private Context mContext; // 应用上下文
private ContentResolver mContentResolver; // 内容解析器
private boolean mSyncing; // 同步状态标志
private boolean mCancelled; // 取消状态标志
// 数据存储映射
private HashMap<String, TaskList> mGTaskListHashMap; // Google任务列表映射
private HashMap<String, Node> mGTaskHashMap; // Google任务映射
private HashMap<String, MetaData> mMetaHashMap; // 元数据映射
private TaskList mMetaList; // 元数据列表
private HashSet<Long> mLocalDeleteIdMap; // 本地删除ID映射
private HashMap<String, Long> mGidToNid; // Google ID到本地ID的映射
private HashMap<Long, String> mNidToGid; // 本地ID到Google ID的映射
/**
*
*/
private GTaskManager() {
mSyncing = false;
mCancelled = false;
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
/**
* GTaskManager
*/
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
/**
*
*/
public synchronized void setActivityContext(Activity activity) {
mActivity = activity;
}
/**
*
* @param context
* @param asyncTask
* @return
*/
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// 登录Google任务
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// 从Google获取任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 执行内容同步
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
/**
* Google
* Google
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
JSONArray jsTaskLists = client.getTaskLists();
// 首先初始化元数据列表
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
metaData.setContentByRemoteJSON(object);
if (metaData.isWorthSaving()) {
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// 如果元数据列表不存在则创建
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
TaskList tasklist = new TaskList();
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// 加载任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid));
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
/**
*
*
*/
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
mLocalDeleteIdMap.clear();
if (mCancelled) {
return;
}
// 处理本地删除的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 首先同步文件夹
syncFolder();
// 处理数据库中已存在的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing note in database");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理剩余的项目
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// 清除本地删除表
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// 刷新本地同步ID
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
/**
*
*
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
if (mCancelled) {
return;
}
// 处理根文件夹
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null) {
c.moveToNext();
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
} else {
Log.w(TAG, "failed to query root folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理通话记录文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
}
} else {
Log.w(TAG, "failed to query call note folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理本地已存在的文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理远程添加的文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
/**
*
* @param syncType
* @param node
* @param c
*/
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
MetaData meta;
switch (syncType) {
case Node.SYNC_ACTION_ADD_LOCAL:
addLocalNode(node);
break;
case Node.SYNC_ACTION_ADD_REMOTE:
addRemoteNode(node, c);
break;
case Node.SYNC_ACTION_DEL_LOCAL:
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
case Node.SYNC_ACTION_DEL_REMOTE:
meta = mMetaHashMap.get(node.getGid());
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
GTaskClient.getInstance().deleteNode(node);
break;
case Node.SYNC_ACTION_UPDATE_LOCAL:
updateLocalNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_REMOTE:
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// 合并两个修改可能是个好主意
// 现在只是简单地使用本地更新
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
break;
case Node.SYNC_ACTION_ERROR:
default:
throw new ActionFailureException("unkown sync action type");
}
}
/**
*
*
*/
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
if (node instanceof TaskList) {
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else {
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// ID不可用必须创建新的
note.remove(NoteColumns.ID);
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// 数据ID不可用必须创建新的
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js);
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
sqlNote.setParentId(parentId.longValue());
}
// 创建本地节点
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// 更新gid-nid映射
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
*
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// 本地更新笔记
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// 更新元信息
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
*
*/
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// 远程更新
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task");
}
mGTaskListHashMap.get(parentGid).addChildTask(task);
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// 添加元数据
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// 如果文件夹已存在,我们需要跳过
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT;
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
else
folderName += sqlNote.getSnippet();
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// 没有匹配项,现在可以添加
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
}
// 更新本地笔记
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id映射
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
/**
*
*
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
// 远程更新
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote);
// 必要时移动任务
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent();
String curParentGid = mNidToGid.get(sqlNote.getParentId());
if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
if (preParentList != curParentList) {
preParentList.removeChildTask(task);
curParentList.addChildTask(task);
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// 清除本地修改标志
sqlNote.resetLocalModified();
sqlNote.commit(true);
}
/**
*
* @param gid Google ID
* @param sqlNote SQL
*/
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
metaData.setMeta(gid, sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
mMetaHashMap.put(gid, metaData);
GTaskClient.getInstance().createTask(metaData);
}
}
}
/**
* ID
* ID
*/
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// 获取最新的gtask列表
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
initGTaskList();
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
Node node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
ContentValues values = new ContentValues();
values.put(NoteColumns.SYNC_ID, node.getLastModified());
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
Log.e(TAG, "something is missed");
throw new ActionFailureException(
"some local items don't have gid after sync");
}
}
} else {
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
}
/**
*
* @return
*/
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
/**
*
*/
public void cancelSync() {
mCancelled = true;
}
}

@ -1,173 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
/**
* Google Tasks
* Google Tasks广UI
*/
public class GTaskSyncService extends Service {
// 服务操作类型常量
public final static String ACTION_STRING_NAME = "sync_action_type"; // 操作类型键名
public final static int ACTION_START_SYNC = 0; // 启动同步
public final static int ACTION_CANCEL_SYNC = 1; // 取消同步
public final static int ACTION_INVALID = 2; // 无效操作
// 广播相关常量
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; // 广播名称
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 是否正在同步
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 同步进度消息
private static GTaskASyncTask mSyncTask = null; // 同步任务实例
private static String mSyncProgress = ""; // 同步进度消息
/**
*
*/
private void startSync() {
if (mSyncTask == null) { // 避免重复启动
// 创建并执行异步同步任务
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
@Override
public void onComplete() {
// 同步完成后清理资源
mSyncTask = null;
sendBroadcast(""); // 发送同步完成广播
stopSelf(); // 停止服务
}
});
sendBroadcast(""); // 发送同步开始广播
mSyncTask.execute(); // 执行同步任务
}
}
/**
*
*/
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync(); // 调用异步任务的取消方法
}
}
@Override
public void onCreate() {
super.onCreate();
mSyncTask = null; // 服务创建时初始化同步任务
}
/**
*
* @param intent
* @param flags
* @param startId ID
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
// 根据操作类型执行相应操作
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
startSync();
break;
case ACTION_CANCEL_SYNC:
cancelSync();
break;
default:
break;
}
return START_STICKY; // 服务被杀死后尝试重新启动
}
return super.onStartCommand(intent, flags, startId);
}
/**
*
*
*/
@Override
public void onLowMemory() {
super.onLowMemory();
if (mSyncTask != null) {
mSyncTask.cancelSync(); // 内存不足时取消同步
}
}
@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);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 是否正在同步
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 同步消息
sendBroadcast(intent); // 发送广播
}
// ==================== 静态调用方法 ====================
/**
*
* @param activity Activity
*/
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity); // 设置Activity上下文
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(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
*/
public static boolean isSyncing() {
return mSyncTask != null;
}
/**
*
* @return
*/
public static String getProgressString() {
return mSyncProgress;
}
}

@ -1,341 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
/**
*
*
*/
public class Note {
private ContentValues mNoteDiffValues; // 笔记差异值(待更新的笔记属性)
private NoteData mNoteData; // 笔记关联数据
private static final String TAG = "Note";
/**
* ID
* @param context
* @param folderId ID
* @return ID
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// 创建新笔记的基本信息
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime); // 创建时间
values.put(NoteColumns.MODIFIED_DATE, createdTime); // 修改时间
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 笔记类型
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地已修改
values.put(NoteColumns.PARENT_ID, folderId); // 父文件夹ID
// 插入数据库并获取URI
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
noteId = Long.valueOf(uri.getPathSegments().get(1)); // 从URI中解析笔记ID
} catch (NumberFormatException e) {
Log.e(TAG, "获取笔记ID错误: " + e.toString());
noteId = 0;
}
if (noteId == -1) {
throw new IllegalStateException("错误的笔记ID: " + noteId);
}
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());
}
/**
*
* @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);
}
/**
*
* @param key
* @param value
*/
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
/**
*
* @return
*/
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
*
* @param context
* @param noteId ID
* @return
*/
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("错误的笔记ID: " + noteId);
}
if (!isLocalModified()) {
return true; // 没有修改,无需同步
}
/**
* LOCAL_MODIFIEDMODIFIED_DATE
* 使
*/
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
mNoteDiffValues, null, null) == 0) {
Log.e(TAG, "更新笔记错误,这种情况不应该发生");
// 不返回,继续处理数据更新
}
mNoteDiffValues.clear(); // 清除已同步的差异值
// 同步笔记关联数据
if (mNoteData.isLocalModified() &&
(mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
}
return true;
}
/**
*
*
*/
private class NoteData {
private long mTextDataId; // 文本数据ID
private ContentValues mTextDataValues; // 文本数据值
private long mCallDataId; // 通话记录数据ID
private ContentValues mCallDataValues; // 通话记录数据值
private static final String TAG = "NoteData";
/**
*
*/
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
/**
*
* @return
*/
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
/**
* ID
* @param id ID
*/
void setTextDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("文本数据ID应该大于0");
}
mTextDataId = id;
}
/**
* ID
* @param id ID
*/
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("通话记录数据ID应该大于0");
}
mCallDataId = id;
}
/**
*
* @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());
}
/**
*
* @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 URI
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
*
*/
if (noteId <= 0) {
throw new IllegalArgumentException("错误的笔记ID: " + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
ContentProviderOperation.Builder builder = null;
// 处理文本数据
if (mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId); // 设置关联的笔记ID
if (mTextDataId == 0) {
// 新建文本数据
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新ID
} catch (NumberFormatException e) {
Log.e(TAG, "插入新文本数据失败笔记ID: " + noteId);
mTextDataValues.clear();
return null;
}
} else {
// 更新现有文本数据
builder = ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
mTextDataValues.clear(); // 清除已处理的数据
}
// 处理通话记录数据
if (mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId); // 设置关联的笔记ID
if (mCallDataId == 0) {
// 新建通话记录数据
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新ID
} catch (NumberFormatException e) {
Log.e(TAG, "插入新通话记录数据失败笔记ID: " + noteId);
mCallDataValues.clear();
return null;
}
} else {
// 更新现有通话记录数据
builder = ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear(); // 清除已处理的数据
}
// 批量执行操作
if (operationList.size() > 0) {
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "批量操作异常: " + e.getMessage());
return null;
}
}
return null;
}
}
}

@ -1,530 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
/**
*
*
*/
public class WorkingNote {
// 笔记对象
private Note mNote;
// 笔记ID
private long mNoteId;
// 笔记内容
private String mContent;
// 笔记模式(普通文本或待办列表)
private int mMode;
// 提醒日期
private long mAlertDate;
// 修改日期
private long mModifiedDate;
// 背景颜色ID
private int mBgColorId;
// 小部件ID
private int mWidgetId;
// 小部件类型
private int mWidgetType;
// 文件夹ID
private long mFolderId;
// 字体加粗状态
private boolean mBoldState;
// 上下文
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,
TextNote.BOLD,
};
// 笔记查询投影
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
// 数据列索引
private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3;
private static final int DATA_BOLD_COLUMN = 7;
// 笔记列索引
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
private static final int NOTE_WIDGET_ID_COLUMN = 3;
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
/**
*
* @param context
* @param folderId ID
*/
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
mModifiedDate = System.currentTimeMillis();
mFolderId = folderId;
mNote = new Note();
mNoteId = 0;
mIsDeleted = false;
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
/**
*
* @param context
* @param noteId ID
* @param folderId ID
*/
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote(); // 加载笔记数据
}
/**
*
*/
private void loadNote() {
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId),
NOTE_PROJECTION, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
// 读取笔记基本信息
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
}
cursor.close();
} else {
Log.e(TAG, "找不到ID为" + mNoteId + "的笔记");
throw new IllegalArgumentException("无法找到ID为" + mNoteId + "的笔记");
}
loadNoteData(); // 加载笔记详细数据
}
/**
*
*/
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?",
new String[] { String.valueOf(mNoteId) }, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) {
// 普通文本笔记数据
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
mBoldState = (cursor.getInt(DATA_BOLD_COLUMN) == 1 ? true : false);
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 通话记录笔记数据
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
Log.d(TAG, "错误的笔记类型: " + type);
}
} while (cursor.moveToNext());
}
cursor.close();
} else {
Log.e(TAG, "找不到ID为" + mNoteId + "的笔记数据");
throw new IllegalArgumentException("无法找到ID为" + mNoteId + "的笔记数据");
}
}
/**
*
* @param context
* @param folderId ID
* @param widgetId ID
* @param widgetType
* @param defaultBgColorId ID
* @return
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
return note;
}
/**
*
* @param context
* @param id ID
* @return
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
/**
*
* @return
*/
public synchronized boolean saveNote() {
if (isWorthSaving()) { // 检查是否值得保存
if (!existInDatabase()) { // 如果笔记还不存在于数据库中
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "创建新笔记失败ID: " + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId); // 同步笔记数据到数据库
/**
*
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
/**
*
* @return
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
*
* @return
*/
private boolean isWorthSaving() {
// 如果笔记已删除,或新建笔记但内容为空,或已存在但无修改,则不值得保存
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
return true;
}
}
/**
*
* @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;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
/**
*
* @param mark
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
/**
*
* @param id ID
*/
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
/**
*
* @param mode
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
/**
*
* @param type
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
/**
* ID
* @param id ID
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
/**
*
* @param text
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
/**
*
* @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
*/
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;
}
/**
*
* @param bold
*/
public void setBoldState(boolean bold) {
if (mBoldState != bold) {
mBoldState = bold;
mNote.setTextData(TextNote.BOLD, (mBoldState ? "1" : "0"));
mNote.setNoteValue(NoteColumns.SNIPPET, getContent());
if (mNoteSettingStatusListener != null) {
// 可选添加一个回调接口方法来通知UI加粗状态已更改
// mNoteSettingStatusListener.onBoldStateChanged(bold);
}
}
}
/**
*
* @return
*/
public boolean getBoldState() {
return mBoldState;
}
/**
*
*/
public interface NoteSettingChangedListener {
/**
*
*/
void onBackgroundColorChanged();
/**
*
*/
void onClockAlertChanged(long date, boolean set);
/**
*
*/
void onWidgetChanged();
/**
*
* @param oldMode
* @param newMode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -1,416 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
/**
*
*/
public class BackupUtils {
private static final String TAG = "BackupUtils";
// 单例模式实例
private static BackupUtils sInstance;
/**
* BackupUtils
*
* @param context
* @return BackupUtils
*/
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
*
*/
// 当前SD卡未挂载
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 备份文件不存在
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;
/**
* TextExport
*
* @param context
*/
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
/**
*
*
* @return truefalse
*/
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;
}
/**
*
*/
private static class TextExport {
// 用于查询笔记的投影列
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
// 笔记ID列的索引
private static final int NOTE_COLUMN_ID = 0;
// 笔记修改日期列的索引
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
// 笔记摘要列的索引
private static final int NOTE_COLUMN_SNIPPET = 2;
// 用于查询笔记数据的投影列
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
// 笔记数据内容列的索引
private static final int DATA_COLUMN_CONTENT = 0;
// 笔记数据MIME类型列的索引
private static final int DATA_COLUMN_MIME_TYPE = 1;
// 通话记录日期列的索引
private static final int DATA_COLUMN_CALL_DATE = 2;
// 电话号码列的索引
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
// 文本导出格式数组
private final String[] TEXT_FORMAT;
// 文件夹名称格式索引
private static final int FORMAT_FOLDER_NAME = 0;
// 笔记日期格式索引
private static final int FORMAT_NOTE_DATE = 1;
// 笔记内容格式索引
private static final int FORMAT_NOTE_CONTENT = 2;
private Context mContext;
private String mFileName;
private String mFileDirectory;
/**
*
*
* @param context
*/
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
/**
*
*
* @param id
* @return
*/
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
*
*
* @param folderId ID
* @param ps
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询属于该文件夹的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[]{
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// 打印笔记的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于该笔记的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
*
*
* @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
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// 打印电话号码
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));
}
// 打印通话日期
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// 打印通话附件位置
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// 在笔记之间打印换行符
try {
ps.write(new byte[]{
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
*
*
* @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;
}
// 首先导出文件夹及其笔记
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// 打印文件夹名称
String folderName = "";
if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// 导出根文件夹下的笔记
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于该笔记的数据
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
/**
*
*
* @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);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
*
*
* @param context
* @param filePathResId ID
* @param fileNameFormatResId ID
* @return null
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try {
if (!filedir.exists()) {
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

@ -1,389 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
/**
*
*/
public class DataUtils {
public static final String TAG = "DataUtils";
/**
*
*
* @param resolver
* @param ids ID
* @return truefalse
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
if (id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
/**
*
*
* @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);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
/**
*
*
* @param resolver
* @param ids ID
* @param folderId ID
* @return truefalse
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
/**
*
*
* @param resolver
* @return
*/
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String[]{"COUNT(*)"},
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[]{String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close();
}
}
}
return count;
}
/**
*
*
* @param resolver
* @param noteId ID
* @param type
* @return truefalse
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String[]{String.valueOf(type)},
null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
*
*
* @param resolver
* @param noteId ID
* @return truefalse
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
*
*
* @param resolver
* @param dataId ID
* @return truefalse
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
*
*
* @param resolver
* @param name
* @return truefalse
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[]{name}, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
*
*
* @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},
NoteColumns.PARENT_ID + "=?",
new String[]{String.valueOf(folderId)},
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>();
do {
try {
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
set.add(widget);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
c.close();
}
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},
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String[]{String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE},
null);
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
cursor.close();
}
}
return "";
}
/**
* ID
*
* @param resolver
* @param phoneNumber
* @param callDate
* @return ID0
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String[]{CallNote.NOTE_ID},
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
new String[]{String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber},
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
cursor.close();
}
return 0;
}
/**
* ID
*
* @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},
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)},
null);
if (cursor != null) {
String snippet = "";
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
cursor.close();
return snippet;
}
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();
int index = snippet.indexOf('\n');
if (index != -1) {
snippet = snippet.substring(0, index);
}
}
return snippet;
}
}

@ -1,161 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
/**
* GoogleGTaskJSONGTaskJSON
*/
public class GTaskStringUtils {
// 定义GTask JSON数据中的动作ID字段
public final static String GTASK_JSON_ACTION_ID = "action_id";
// 定义GTask JSON数据中的动作列表字段
public final static String GTASK_JSON_ACTION_LIST = "action_list";
// 定义GTask JSON数据中的动作类型字段
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
// 定义GTask JSON数据中的创建动作类型
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
// 定义GTask JSON数据中的获取所有动作类型
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
// 定义GTask JSON数据中的移动动作类型
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
// 定义GTask JSON数据中的更新动作类型
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
// 定义GTask JSON数据中的创建者ID字段
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
// 定义GTask JSON数据中的子实体字段
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
// 定义GTask JSON数据中的客户端版本字段
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
// 定义GTask JSON数据中的完成状态字段
public final static String GTASK_JSON_COMPLETED = "completed";
// 定义GTask JSON数据中的当前列表ID字段
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
// 定义GTask JSON数据中的默认列表ID字段
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
// 定义GTask JSON数据中的删除状态字段
public final static String GTASK_JSON_DELETED = "deleted";
// 定义GTask JSON数据中的目标列表字段
public final static String GTASK_JSON_DEST_LIST = "dest_list";
// 定义GTask JSON数据中的目标父级字段
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
// 定义GTask JSON数据中的目标父级类型字段
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
// 定义GTask JSON数据中的实体增量字段
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
// 定义GTask JSON数据中的实体类型字段
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
// 定义GTask JSON数据中的获取已删除项字段
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
// 定义GTask JSON数据中的ID字段
public final static String GTASK_JSON_ID = "id";
// 定义GTask JSON数据中的索引字段
public final static String GTASK_JSON_INDEX = "index";
// 定义GTask JSON数据中的最后修改时间字段
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
// 定义GTask JSON数据中的最新同步点字段
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
// 定义GTask JSON数据中的列表ID字段
public final static String GTASK_JSON_LIST_ID = "list_id";
// 定义GTask JSON数据中的列表集合字段
public final static String GTASK_JSON_LISTS = "lists";
// 定义GTask JSON数据中的名称字段
public final static String GTASK_JSON_NAME = "name";
// 定义GTask JSON数据中的新ID字段
public final static String GTASK_JSON_NEW_ID = "new_id";
// 定义GTask JSON数据中的备注字段
public final static String GTASK_JSON_NOTES = "notes";
// 定义GTask JSON数据中的父级ID字段
public final static String GTASK_JSON_PARENT_ID = "parent_id";
// 定义GTask JSON数据中的前一个兄弟ID字段
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
// 定义GTask JSON数据中的结果字段
public final static String GTASK_JSON_RESULTS = "results";
// 定义GTask JSON数据中的源列表字段
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
// 定义GTask JSON数据中的任务集合字段
public final static String GTASK_JSON_TASKS = "tasks";
// 定义GTask JSON数据中的类型字段
public final static String GTASK_JSON_TYPE = "type";
// 定义GTask JSON数据中的组类型
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
// 定义GTask JSON数据中的任务类型
public final static String GTASK_JSON_TYPE_TASK = "TASK";
// 定义GTask 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";
// 定义元数据头中的GTask 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";
}

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

@ -1,166 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
/**
*
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId; // 关联的笔记ID
private String mSnippet; // 笔记内容片段
private static final int SNIPPET_PREW_MAX_LEN = 60; // 预览文本最大长度
MediaPlayer mPlayer; // 媒体播放器用于播放闹铃
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 无标题栏
// 设置窗口属性,在锁屏时也能显示
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 如果屏幕关闭,则唤醒屏幕
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 从Intent获取笔记数据
Intent intent = getIntent();
try {
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;
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer();
// 检查笔记是否仍然存在
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog(); // 显示操作对话框
playAlarmSound(); // 播放闹铃声音
} else {
finish(); // 笔记不存在则结束活动
}
}
// 检查屏幕是否亮着
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
// 播放闹铃声音
private void playAlarmSound() {
// 获取默认闹铃铃声
Uri url = 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 {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
// 准备并播放声音
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true); // 循环播放
mPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
// 显示操作对话框
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
dialog.show().setOnDismissListener(this);
}
// 对话框按钮点击事件
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE: // 进入笔记
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
break;
}
}
// 对话框关闭事件
public void onDismiss(DialogInterface dialog) {
stopAlarmSound(); // 停止闹铃
finish(); // 结束活动
}
// 停止闹铃声音
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}

@ -1,71 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
* 广
*/
public class AlarmInitReceiver extends BroadcastReceiver {
// 查询笔记的投影(需要哪些列)
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
private static final int COLUMN_ID = 0; // ID列索引
private static final int COLUMN_ALERTED_DATE = 1; // 提醒日期列索引
@Override
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();
// 查询所有设置了未来提醒的笔记
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);
if (c != null) {
if (c.moveToFirst()) {
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 为每个笔记创建闹钟
Intent sender = new Intent(context, AlarmReceiver.class);
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(COLUMN_ID)));
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
c.close();
}
}
}

@ -1,34 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* 广
*/
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 启动闹钟提醒活动
intent.setClass(context, AlarmAlertActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

@ -1,504 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
/**
* DateTimePickerFrameLayout
*
*/
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;
// 上午/下午选择器的最小取值
private static final int AMPM_SPINNER_MIN_VAL = 0;
// 上午/下午选择器的最大取值
private static final int AMPM_SPINNER_MAX_VAL = 1;
// 日期选择器
private final NumberPicker mDateSpinner;
// 小时选择器
private final NumberPicker mHourSpinner;
// 分钟选择器
private final NumberPicker mMinuteSpinner;
// 上午/下午选择器
private final NumberPicker mAmPmSpinner;
// 日历对象,用于存储当前日期和时间
private Calendar mDate;
// 日期显示值数组
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 是否为上午
private boolean mIsAm;
// 是否为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) {
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;
}
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 {
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));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
/**
*
*/
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 获取分钟选择器的最小和最大取值
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
if (oldVal == maxValue && newVal == minValue) {
// 从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();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
// 如果小时数大于等于12设置为下午
mIsAm = false;
// 更新上午/下午选择器的显示
updateAmPmControl();
} else {
// 如果小时数小于12设置为上午
mIsAm = true;
// 更新上午/下午选择器的显示
updateAmPmControl();
}
}
// 设置日历对象的分钟数
mDate.set(Calendar.MINUTE, newVal);
// 调用日期和时间变化的回调方法
onDateTimeChanged();
}
};
/**
* //
*/
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 切换上午/下午状态
mIsAm = !mIsAm;
if (mIsAm) {
// 从下午切换到上午小时数减12
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
// 从上午切换到下午小时数加12
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
// 更新上午/下午选择器的显示
updateAmPmControl
updateAmPmControl();
// 调用日期和时间变化的回调方法
onDateTimeChanged();
}
};
/**
*
*/
public interface OnDateTimeChangedListener {
/**
*
* @param view
* @param year
* @param month
* @param dayOfMonth
* @param hourOfDay
* @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));
}
/**
* 使24
* @param context
* @param date
* @param is24HourView 24
*/
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
// 初始化日历对象
mDate = Calendar.getInstance();
mInitialising = true;
// 判断当前小时数是否大于等于12确定是否为下午
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.setOnValueChangedListener(mOnMinuteChangedListener);
// 获取上午/下午显示字符串数组
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
// 获取上午/下午选择器
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
// 设置上午/下午选择器的最小和最大取值
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
// 设置上午/下午选择器的显示值
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
// 设置上午/下午选择器的值变化监听器
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// 更新日期选择器的显示
updateDateControl();
// 更新小时选择器的显示
updateHourControl();
// 更新上午/下午选择器的显示
updateAmPmControl();
// 设置是否为24小时制
set24HourView(is24HourView);
// 设置当前日期和时间
setCurrentDate(date);
// 设置启用状态
setEnabled(isEnabled());
// 设置内容描述
mInitialising = false;
}
/**
*
* @param enabled
*/
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
// 设置日期选择器的启用状态
mDateSpinner.setEnabled(enabled);
// 设置分钟选择器的启用状态
mMinuteSpinner.setEnabled(enabled);
// 设置小时选择器的启用状态
mHourSpinner.setEnabled(enabled);
// 设置上午/下午选择器的启用状态
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
}
/**
*
* @return
*/
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
*
* @return
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
*
* @param date
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
// 设置当前日期和时间的年、月、日、小时、分钟
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
*
* @param year
* @param month
*/
public void setCurrentDate(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);
mDate.set(Calendar.SECOND, 0);
mDate.set(Calendar.MILLISECOND, 0);
updateDateControl();
updateHourControl();
updateAmPmControl();
}
/**
*
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
mDateSpinner.setValue(dayOfWeek - 1);
}
/**
*
*/
private void updateHourControl() {
int hour = getCurrentHour();
mHourSpinner.setValue(hour);
}
/**
* /
*/
private void updateAmPmControl() {
mAmPmSpinner.setValue(mIsAm ? 0 : 1);
}
/**
*
* @return
*/
private int getCurrentHour() {
int hour = mDate.get(Calendar.HOUR);
if (hour == 0) {
hour = 12;
}
return hour;
}
/**
* 24
* @return 24
*/
private int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
*
* @param year
*/
private void setCurrentYear(int year) {
mDate.set(Calendar.YEAR, year);
}
/**
*
* @param month
*/
private void setCurrentMonth(int month) {
mDate.set(Calendar.MONTH, month);
}
/**
*
* @param dayOfMonth
*/
private void setCurrentDay(int dayOfMonth) {
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
}
/**
* 24
* @param is24HourView 24
*/
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
mAmPmSpinner.setVisibility(View.GONE);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
updateHourControl();
}
/**
*
*/
private void onDateTimeChanged() {
if (!mInitialising && mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, mDate.get(Calendar.YEAR),
mDate.get(Calendar.MONTH), mDate.get(Calendar.DAY_OF_MONTH),
mDate.get(Calendar.HOUR_OF_DAY), mDate.get(Calendar.MINUTE));
}
}
/**
*
* @param listener
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener listener) {
mOnDateTimeChangedListener {insert\_element\_6\_PSBsaXN0ZW5lcjsKICAgIH0KfQpgYGAKCg==}###

@ -1,101 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
/**
*
*/
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance(); // 当前日期
private boolean mIs24HourView; // 是否24小时制
private OnDateTimeSetListener mOnDateTimeSetListener; // 日期时间设置回调
private DateTimePicker mDateTimePicker; // 日期时间选择器
// 日期时间设置回调接口
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker); // 设置自定义视图
// 日期时间变化监听器
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 更新日期对象
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
updateTitle(mDate.getTimeInMillis()); // 更新标题显示
}
});
// 初始化日期
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置按钮
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 设置时间显示格式
set24HourView(DateFormat.is24HourFormat(this.getContext()));
updateTitle(mDate.getTimeInMillis());
}
// 设置24小时制显示
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
// 设置日期时间设置监听器
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
// 更新对话框标题显示
private void updateTitle(long date) {
int flag = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
// 确定按钮点击事件
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

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

@ -1,124 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
* FoldersListAdapterCursorAdapterListView
*
*/
public class FoldersListAdapter extends CursorAdapter {
// 查询数据库时使用的投影列数组,指定要查询的列
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
// 定义各列在投影数组中的索引,方便从游标中获取数据
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
/**
*
* @param context
* @param c
*/
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
/**
*
* @param context
* @param cursor
* @param parent
* @return
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
/**
*
* @param view
* @param context
* @param 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 {
// 显示文件夹名称的TextView
private TextView mName;
/**
*
* @param context
*/
public FolderListItem(Context context) {
super(context);
// 加载文件夹列表项的布局
inflate(context, R.layout.folder_list_item, this);
// 获取显示文件夹名称的TextView
mName = (TextView) findViewById(R.id.tv_folder_name);
}
/**
* TextView
* @param name
*/
public void bind(String name) {
mName.setText(name){insert\_element\_7\_OwogICAgICAgIH0KICAgIH0KfQo=}```
###

@ -1,287 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* NoteEditActivity
*/
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
* ViewHolder
*/
private class HeadViewHolder {
// 显示修改日期的TextView
public TextView tvModified;
// 显示提醒图标的ImageView
public ImageView ivAlertIcon;
// 显示提醒日期的TextView
public TextView tvAlertDate;
// 设置背景颜色的ImageView
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);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
// 背景颜色ID与选择状态图标ID的映射
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
// 字体大小选择按钮与字体大小ID的映射
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
// 字体大小ID与选择状态图标ID的映射
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity";
// 头部视图的ViewHolder
private HeadViewHolder mNoteHeaderHolder;
// 头部视图面板
private View mHeadViewPanel;
// 背景颜色选择器视图
private View mNoteBgColorSelector;
// 字体大小选择器视图
private View mFontSizeSelector;
// 笔记编辑器
private EditText mNoteEditor;
// 笔记编辑器面板
private View mNoteEditorPanel;
// 工作笔记对象
private WorkingNote mWorkingNote;
// 共享偏好设置
private SharedPreferences mSharedPrefs;
// 字体大小ID
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;
/**
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置布局
this.setContentView(R.layout.note_edit);
// 如果保存的实例状态为空且初始化活动状态失败,则关闭活动
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
// 初始化资源
initResources();
}
/**
*
* @param savedInstanceState
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
// 创建一个查看笔记的意图
Intent intent = new Intent(Intent.ACTION_VIEW);
// 从保存的状态中获取笔记ID
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
// 初始化活动状态
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
/**
*
* @param intent
* @return
*/
private boolean initActivityState(Intent intent) {
mWorkingNote = null;
// 如果意图动作是查看笔记
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
// 获取笔记ID
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
// 如果意图包含搜索结果信息
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
}
// 检查笔记是否在数据库中可见
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {
// 笔记不可见,跳转到笔记列表活动
Intent jump = new Intent(this, NotesListActivity.class);
startActivity(jump);
// 显示笔记不存在的提示信息
showToast(R.string.error_note_not_exist);
finish();
return false;
} else {
// 加载笔记数据
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId);
finish();
return false;
}
}
// 设置软键盘状态
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {
// 新建笔记
// 获取文件夹ID
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
// 获取小部件ID
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
// 获取小部件类型
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE);
// 获取背景资源ID
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this));
// 解析通话记录笔记
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
if (callDate != 0 && phoneNumber != null) {
if (TextUtils.isEmpty(phoneNumber)) {
Log.w(TAG, "The call record number is null");
}
long noteId = 0;
// 根据电话号码和通话日期获取笔记ID
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {
// 加载通话记录笔记
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load call note failed with note id" + noteId);
finish();
return false;
}
}
}
}
return true;
}
}

@ -1,294 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
/**
* NoteEditTextEditText
*
*/
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
// 编辑文本在列表中的索引
private int mIndex;
// 删除操作前的选择起始位置
private int mSelectionStartBeforeDelete;
// 定义支持的链接协议
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
// 协议与对应的操作资源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);
}
/**
*
*/
public interface OnTextViewChangeListener {
/**
*
* @param index
* @param text
*/
void onEditTextDelete(int index, String text);
/**
*
* @param index
* @param text
*/
void onEditTextEnter(int index, String text);
/**
*
* @param index
* @param hasText
*/
void onTextChange(int index, boolean hasText);
}
// 文本视图变化监听器
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();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
// 获取文本布局
Layout layout = getLayout();
// 根据坐标获取行号和偏移量
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
// 设置文本选择位置
Selection.setSelection(getText(), off);
break;
}
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:
break;
}
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) {
// 如果选择起始位置为0且不是第一个编辑文本则删除当前编辑文本
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
// 获取选择起始位置
int selectionStart = getSelectionStart();
// 获取选择位置之后的文本
String text = getText().subSequence(selectionStart, length()).toString();
// 设置当前编辑文本为选择位置之前的文本
setText(getText().subSequence(0, selectionStart));
// 在当前编辑文本后插入新的编辑文本
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
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 {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
*
* @param menu
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
// 获取选择的起始和结束位置
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 获取选择范围内的URLSpan
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
// 根据链接协议获取对应的操作资源ID
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
// 添加菜单项并设置点击监听器
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 处理链接点击事件
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}
}

@ -1,338 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
/**
* NoteItemData
* ID
*/
public class NoteItemData {
// 查询数据库时使用的投影列数组,指定要查询的列
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
};
// 定义各列在投影数组中的索引,方便从游标中获取数据
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
// 存储笔记的各种属性
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
// 标记笔记项在列表中的位置信息
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
/**
*
* @param context
* @param cursor
*/
public NoteItemData(Context context, Cursor cursor) {
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 移除笔记摘要中的特定标记
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mPhoneNumber = "";
// 如果笔记的父ID是通话记录文件夹的ID
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
// 获取通话记录的电话号码
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
// 根据电话号码获取联系人姓名
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
if (mName == null) {
mName = "";
}
// 检查笔记项在列表中的位置
checkPostion(cursor);
}
/**
*
* @param cursor
*/
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
// 如果笔记类型是普通笔记且不是第一项
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
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;
}
}
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
/**
*
* @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));
}
/**
*
* @param cursor
* @return
*/
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -1,348 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.R.menu;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
/**
* NotesListActivity
*/
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;
// 文件夹删除菜单的ID
private static final int MENU_FOLDER_DELETE = 0;
// 文件夹查看菜单的ID
private static final int MENU_FOLDER_VIEW = 1;
// 文件夹重命名菜单的ID
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 应用介绍的偏好设置键
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
/**
*
*/
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
};
// 当前列表编辑状态
private ListEditState mState;
// 后台查询处理器
private BackgroundQueryHandler mBackgroundQueryHandler;
// 笔记列表适配器
private NotesListAdapter mNotesListAdapter;
// 笔记列表视图
private ListView mNotesListView;
// 新增笔记按钮
private Button mAddNewNote;
// 菜单设置按钮
private Button mMenuSet;
// 是否分发事件
private boolean mDispatch;
// 初始Y坐标
private int mOriginY;
// 分发Y坐标
private int mDispatchY;
// 标题栏TextView
private TextView mTitleBar;
// 当前文件夹ID
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 static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
// 打开笔记的请求码
private final static int REQUEST_CODE_OPEN_NODE = 102;
// 新建笔记的请求码
private final static int REQUEST_CODE_NEW_NODE = 103;
/**
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置布局
setContentView(R.layout.note_list);
// 初始化资源
initResources();
/**
* Insert an introduction when user firstly use this application
*/
// 当用户首次使用应用时插入介绍笔记
setAppInfoFromRawRes();
}
/**
*
* @param requestCode
* @param resultCode
* @param data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
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)) {
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
// 打开介绍文件的输入流
in = getResources().openRawResource(R.raw.introduction);
if (in != null) {
// 创建输入流读取器和缓冲读取器
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
char [] buf = new char[1024];
int len = 0;
// 读取文件内容
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if(in != null) {
try {
// 关闭输入流
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 创建一个空的工作笔记
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
// 设置笔记内容
note.setWorkingText(sb.toString());
if (note.saveNote()) {
// 保存介绍笔记成功,更新偏好设置
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
Log.e(TAG, "Save introduction note error");
return;
}
}
}
/**
*
*/
@Override
protected void onStart() {
super.onStart();
startAsyncNotesListQuery();
}
/**
*
*/
private void initResources() {
// 获取内容解析器
mContentResolver = this.getContentResolver();
// 创建后台查询处理器
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
// 设置当前文件夹ID为根文件夹ID
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 获取笔记列表视图
mNotesListView = (ListView) findViewById(R.id.notes_list);
// 添加列表页脚视图
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
// 设置列表项点击监听器
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
// 设置列表项长按监听器
mNotesListView.setOnItemLongClickListener(this);
// 创建笔记列表适配器
mNotesListAdapter = new NotesListAdapter(this);
// 设置列表适配器
mNotesListView.setAdapter(mNotesListAdapter);
// 获取新增笔记按钮
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
// 设置新增笔记按钮的点击监听器
mAddNewNote.setOnClickListener(this);
// 设置新增笔记按钮的触摸监听器
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
// 获取菜单设置按钮
mMenuSet = (Button) findViewById(R.id.btn_set);
mDispatch = false;
mDispatchY = 0;
mOriginY = 0;
// 获取标题栏TextView
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
// 设置初始列表编辑状态
mState = ListEditState.NOTE_LIST;
// 创建模式回调对象
mModeCallBack = new ModeCallback();
}
/**
*
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
// 下拉菜单
private DropdownMenu mDropDownMenu;
// 动作模式
private ActionMode mActionMode;
// 菜单
private Menu menu;
// 移动菜单菜单项
private MenuItem mMoveMenu;
/**
*
* @param mode
* @param menu
* @return
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 加载菜单布局
getMenuInflater().inflate(R.menu.note_list_options, menu);
this.menu = menu;
// 设置删除菜单项的点击监听器
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
// 根据当前笔记的父文件夹ID和用户文件夹数量决定移动菜单是否可见
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
// 设置移动菜单的点击监听器
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode;
// 设置适配器为选择模式
mNotesListAdapter.setChoiceMode(true);
// 禁用列表项的长按事件
mNotesListView.setLongClickable(false);
// 隐藏新增笔记按钮
mAddNewNote.setVisibility(View.GONE);
// 隐藏菜单设置按钮
mMenuSet.setVisibility(View.GONE);
// 加载自定义视图
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
// 设置动作模式的自定义视图
mode.setCustomView(customView);
return true;
}
}
}

@ -1,278 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/**
* NotesListAdapterCursorAdapterListView
*
*/
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter";
// 上下文对象
private Context mContext;
// 存储列表项的选择状态,键为列表项的位置,值为是否选中
private HashMap<Integer, Boolean> mSelectedIndex;
// 笔记的数量
private int mNotesCount;
// 是否处于选择模式
private boolean mChoiceMode;
/**
*
*/
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>();
mContext = context;
mNotesCount = 0;
}
/**
*
* @param context
* @param cursor
* @param parent
* @return
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
/**
*
* @param view
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
// 创建笔记项数据对象
NoteItemData itemData = new NoteItemData(context, cursor);
// 将数据绑定到列表项视图上
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
/**
*
* @param position
* @param checked
*/
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
// 通知适配器数据发生变化
notifyDataSetChanged();
}
/**
*
* @return truefalse
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/**
*
* @param mode
*/
public void setChoiceMode(boolean mode) {
// 清空选择状态
mSelectedIndex.clear();
mChoiceMode = mode;
}
/**
*
* @param checked
*/
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);
}
}
}
}
/**
* ID
* @return ID
*/
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
// 获取列表项的ID
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
/**
*
* @return
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
// 获取列表项对应的游标
Cursor c = (Cursor) getItem(position);
if (c != null) {
// 创建小部件属性对象
AppWidgetAttribute widget = new AppWidgetAttribute();
// 创建笔记项数据对象
NoteItemData item = new NoteItemData(mContext, c);
// 设置小部件ID
widget.widgetId = item.getWidgetId();
// 设置小部件类型
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
/**
*
* @return
*/
public int getSelectedCount() {
// 获取选择状态的集合
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
// 遍历集合,统计选中项的数量
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
/**
*
* @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;
}
return mSelectedIndex.get(position);
}
/**
*
*/
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
/**
*
* @param cursor
*/
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
/**
*
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
// 获取列表项对应的游标
Cursor c = (Cursor) getItem(i);
if (c != null) {
// 如果是普通笔记类型,则增加笔记数量
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -1,141 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/**
*
*/
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 提醒图标
private TextView mTitle; // 标题
private TextView mTime; // 时间
private TextView mCallName; // 通话记录名称
private NoteItemData mItemData; // 笔记数据
private CheckBox mCheckBox; // 复选框
public NotesListItem(Context context) {
super(context);
// 加载布局
inflate(context, R.layout.note_item, this);
// 初始化视图
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
// 绑定数据到视图
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 设置复选框可见性
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
// 根据笔记类型设置不同显示
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 通话记录文件夹
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 通话记录
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 设置提醒图标
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
// 普通笔记或文件夹
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) {
// 文件夹
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
// 普通笔记
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 设置提醒图标
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
// 设置相对时间显示
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置背景
setBackground(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()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
// 获取笔记数据
public NoteItemData getItemData() {
return mItemData;
}
}

@ -1,402 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/**
*
*/
public class NotesPreferenceActivity extends PreferenceActivity {
// 偏好设置文件名
public static final String PREFERENCE_NAME = "notes_preferences";
// 同步账户名称键
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
// 最后同步时间键
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
// 背景颜色键
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
// 同步账户分类键
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
// 权限过滤器键
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory; // 账户分类
private GTaskReceiver mReceiver; // 同步任务广播接收器
private Account[] mOriAccounts; // 原始账户列表
private boolean mHasAddedAccount; // 是否添加了新账户
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 启用返回按钮
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从XML加载偏好设置
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);
}
@Override
protected void onResume() {
super.onResume();
// 如果用户添加了新账户,自动设置同步账户
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
}
refreshUI(); // 刷新UI
}
@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)) {
// 首次设置账户
showSelectAccountAlertDialog();
} else {
// 已设置账户,提示风险
showChangeAccountConfirmAlertDialog();
}
} else {
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
mAccountCategory.addPreference(accountPref);
}
// 加载同步按钮
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 根据同步状态设置按钮
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)));
// 设置最后同步时间显示
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);
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
// 创建账户选择列表
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
items[index++] = account.name;
}
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI();
}
});
}
// 添加"添加账户"视图
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
// 启动添加账户设置
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { "gmail-ls" });
startActivityForResult(intent, -1);
dialog.dismiss();
}
});
}
// 显示更改账户确认对话框
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 设置自定义标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
dialogBuilder.setCustomTitle(titleView);
// 设置操作项
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
showSelectAccountAlertDialog(); // 更改账户
} else if (which == 1) {
removeSyncAccount(); // 移除账户
refreshUI();
}
}
});
dialogBuilder.show();
}
// 获取Google账户列表
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
// 设置同步账户
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
editor.commit();
// 清除最后同步时间
setLastSyncTime(this, 0);
// 清除本地GTask相关信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
// 移除同步账户
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
editor.commit();
// 清除本地GTask相关信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
}
// 获取同步账户名称
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 设置最后同步时间
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit();
}
// 获取最后同步时间
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
// 同步任务广播接收器
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
refreshUI(); // 刷新UI
// 更新同步状态显示
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
// 选项菜单选择处理
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);
return true;
default:
return false;
}
}
}

@ -1,218 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import android.widget.RemoteViews;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
/**
* NoteWidgetProvider AppWidgetProvider便
*
*/
public abstract class NoteWidgetProvider extends AppWidgetProvider {
// 定义查询便签信息时使用的投影,指定要查询的列
public static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};
// 定义投影中各列的索引,方便后续使用
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
// 日志标签,用于在日志中标识该类的输出信息
private static final String TAG = "NoteWidgetProvider";
/**
*
* 便 widget_id
*
* @param context ContentResolver
* @param appWidgetIds ID
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// 创建 ContentValues 对象,用于存储要更新的数据
ContentValues values = new ContentValues();
// 将便签的 widget_id 字段设置为无效值
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])});
}
}
/**
* ID 便
* ID 便
*
* @param context ContentResolver
* @param widgetId ID
* @return Cursor
*/
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
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);
}
/**
*
* update false
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
/**
*
* ID ID 便
*
* @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) {
// 获取默认的背景颜色 ID
int bgId = ResourceParser.getDefaultBgId(context);
// 初始化便签的摘要信息
String snippet = "";
// 创建一个 Intent 对象,用于启动便签编辑活动
Intent intent = new Intent(context, NoteEditActivity.class);
// 设置 Intent 的标志,确保活动以单顶模式启动
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
// 将小部件 ID 作为额外信息添加到 Intent 中
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
// 将小部件类型作为额外信息添加到 Intent 中
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
// 根据小部件 ID 查询便签信息
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
// 检查查询结果是否包含多个便签,如果是则记录错误信息并关闭 Cursor
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
// 获取便签的摘要信息
snippet = c.getString(COLUMN_SNIPPET);
// 获取便签的背景颜色 ID
bgId = c.getInt(COLUMN_BG_COLOR_ID);
// 将便签的 ID 作为额外信息添加到 Intent 中
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
// 设置 Intent 的动作,用于查看便签
intent.setAction(Intent.ACTION_VIEW);
} else {
// 如果没有查询到便签信息,设置默认的摘要信息
snippet = context.getResources().getString(R.string.widget_havenot_content);
// 设置 Intent 的动作,用于插入或编辑便签
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
// 关闭 Cursor释放资源
if (c != null) {
c.close();
}
// 创建 RemoteViews 对象,用于更新小部件的视图
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
// 设置小部件的背景图片资源
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
// 将背景颜色 ID 作为额外信息添加到 Intent 中
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;
if (privacyMode) {
// 如果开启隐私模式,设置小部件的文本为隐私模式提示信息
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
// 创建一个 PendingIntent 对象,用于启动便签列表活动
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
// 如果不开启隐私模式,设置小部件的文本为便签的摘要信息
rv.setTextViewText(R.id.widget_text, snippet);
// 创建一个 PendingIntent 对象,用于启动便签编辑活动
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
// 设置小部件文本的点击事件,关联 PendingIntent
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
// 更新指定 ID 的小部件的视图
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
/**
* ID
*
* @param bgId ID
* @return ID
*/
protected abstract int getBgResourceId(int bgId);
/**
* ID
*
* @return ID
*/
protected abstract int getLayoutId();
/**
*
*
* @return
*/
protected abstract int getWidgetType();
}

@ -1,75 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* NoteWidgetProvider_2x NoteWidgetProvider 2x 便
* NoteWidgetProvider 2x
*/
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
/**
*
* update 2x
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* 2x ID
*
* @return 2x ID
*/
@Override
protected int getLayoutId() {
return R.layout.widget_2x;
}
/**
* 2x ID
* ResourceParser ID ID
*
* @param bgId ID
* @return 2x ID
*/
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
/**
* 2x
*
* @return 2x
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;
}
}

@ -1,74 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* NoteWidgetProvider_4x NoteWidgetProvider 4x 便
* NoteWidgetProvider 4x
*/
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
/**
*
* update 4x
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* 4x ID
*
* @return 4x ID
*/
protected int getLayoutId() {
return R.layout.widget_4x;
}
/**
* 4x ID
* ResourceParser ID ID
*
* @param bgId ID
* @return 4x ID
*/
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
/**
* 4x
*
* @return 4x
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_4X;
}
}

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#88555555" />
<item android:state_selected="true" android:color="#ff999999" />
<item android:color="#ff000000" />
</selector>

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

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

Loading…
Cancel
Save