Compare commits

...

3 Commits

Author SHA1 Message Date
zyh 9cea0b3004 zyh
3 years ago
zyh f486ed18fb zyh
3 years ago
zyh 6952e12baf branch create
3 years ago

@ -1,56 +0,0 @@
## 1.克隆仓库
```
git clone 远程url 地址
```
## 2.转移目录至clone文件所在目录
```
cd 目录文件
```
## 3.将所需上传文件拖动到本地文件夹,然后上传到缓存区
```
git add .
```
## 4.查看当前缓存状态
```
git status
```
## 5.提交
```
git commit -m "提交信息"
```
## 6.本地仓库与 远程仓库创建联系
```
git push origin 远程仓库分支名字
```
# 高级操作
```
git branch 分支名字
```

@ -0,0 +1 @@
## this is zyh code page

@ -0,0 +1,37 @@
*.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

@ -0,0 +1,201 @@
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.

@ -0,0 +1,24 @@
# 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

@ -0,0 +1,20 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "25.0.1"
useLibrary 'org.apache.http.legacy'
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' }
}

@ -0,0 +1,152 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
package="net.micode.notes"
android:versionCode="1"
android:versionName="0.1" >
<uses-sdk android:minSdkVersion="14"
tools:ignore="GradleOverrides" />
<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>

@ -0,0 +1,75 @@
package net.micode.notes.data;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
public class Contact {
// 声明一个用于缓存联系人的HashMap
private static HashMap<String, String> sContactCache;
// 声明日志输出的TAG
private static final String TAG = "Contact";
// 定义查询联系人的选择条件,使用占位符 "?" 和 "IN" 子句
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
/**
*
* @param context
* @param phoneNumber
* @return null
*/
public static String getContact(Context context, String phoneNumber) {
// 如果联系人缓存为空则创建一个新的HashMap
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 如果联系人缓存中已经存在该电话号码的联系人,则直接返回联系人名称
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
// 使用PhoneNumberUtils工具类将电话号码转换为拨号最小匹配
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 查询联系人
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
// 如果查询到联系人,则将联系人名称存入缓存并返回联系人名称
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 {
// 如果找不到联系人则打印日志并返回null
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

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

@ -0,0 +1,377 @@
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"; // 日志tag
private static NotesDatabaseHelper mInstance; // 数据库帮助类实例
// 创建笔记数据表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
// 创建笔记内容数据表的SQL语句
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
DataColumns.MIME_TYPE + " TEXT NOT NULL," +
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA1 + " INTEGER," +
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";
/**
*SQLId
*/
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
//创建一个数据表的索引,用于加速查询和过滤操作。
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.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";
/**
* {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* {@link DataConstants#NOTE}
*/
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";
/**
* {@link DataConstants#NOTE}
*/
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");
}
/**
@param db
*/
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
// 删除更新时增加文件夹计数的触发器
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
// 删除更新时减少文件夹计数的触发器
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
// 删除删除时减少文件夹计数的触发器
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);
// 创建将笔记移动到垃圾桶时的触发器
}
/**
@param db
*/
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); // 在数据表上创建笔记 ID 索引
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; // 是否跳过版本 2
if (oldVersion == 1) {
upgradeToV2(db); // 升级到版本 2
skipV2 = true; // 标记跳过版本 2因为版本 2 升级包含在版本 3 升级中)
oldVersion++; // 版本号加 1
}
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); // 升级到版本 3
reCreateTriggers = true; // 标记需要重新创建触发器
oldVersion++; // 版本号加 1
}
if (oldVersion == 3) {
upgradeToV4(db); // 升级到版本 4
oldVersion++; // 版本号加 1
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db); // 重新创建笔记表触发器
reCreateDataTableTriggers(db); // 重新创建数据表触发器
}
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails"); // 如果版本升级失败,抛出异常
}
}
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db);
createDataTable(db);
}
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -0,0 +1,364 @@
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider {
// 声明一个 UriMatcher 对象,用于匹配 Uri 和对应的代码
private static final UriMatcher mMatcher;
private NotesDatabaseHelper mHelper;
private static final String TAG = "NotesProvider";
// 声明一些常量,用于标识 Uri 匹配的类型
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
private static final int URI_DATA_ITEM = 4;
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
// 初始化 UriMatcher 对象,将 Uri 和对应的类型添加到 UriMatcher 中
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
/****************/
/**
* x'0A' sqlite '\n' '\n'
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
/**
* SQL
*/
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
@Override
public boolean onCreate() {
// 获取数据库帮助类的实例
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
/******/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// 初始化游标和数据库
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase();
// 初始化 id
String id = null;
// 根据 Uri 的不同,执行不同的查询操作
switch (mMatcher.match(uri)) {
// 查询所有笔记
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
// 根据 id 查询笔记
case URI_NOTE_ITEM:
// 获取 Uri 中的 id
id = uri.getPathSegments().get(1);
// 根据 id 查询笔记
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
// 查询所有数据
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
// 根据 id 查询数据
case URI_DATA_ITEM:
// 获取 Uri 中的 id
id = uri.getPathSegments().get(1);
// 根据 id 查询数据
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
// 查询笔记的搜索结果
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
// 如果查询中包含排序或者 projection则抛出异常
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
// 获取查询的关键词
String searchString = null;
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
searchString = uri.getQueryParameter("pattern");
}
// 如果关键词为空,则返回空游标
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
// 格式化查询字符串
searchString = String.format("%%%s%%", searchString);
// 执行笔记搜索查询
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[]{searchString});
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
// 如果 Uri 不匹配,则抛出异常
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果游标不为空,则设置游标的内容变化通知 Uri
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
/***********/
@Override
public Uri insert(Uri uri, ContentValues values) {
// 获取可写的 SQLiteDatabase 实例
SQLiteDatabase db = mHelper.getWritableDatabase();
// 定义变量用于存储插入后的数据行 ID
long dataId = 0, noteId = 0, insertedId = 0;
// 根据传入的 Uri 进行不同的插入操作
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 如果是插入 Note 数据表
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
// 如果是插入 Data 数据表
if (values.containsKey(DataColumns.NOTE_ID)) {
// 获取 Note ID
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
// 如果 ContentValues 中没有 Note ID记录日志并退出方法
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
// 插入数据行
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
// 如果传入的 Uri 不合法,抛出 IllegalArgumentException 异常
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果插入操作成功,通知相关 Uri 的观察者
// 通知 Note Uri
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 通知 Data Uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回新插入数据的 Uri
return ContentUris.withAppendedId(uri, insertedId);
}
/*******************/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 初始化变量
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
// 使用 switch-case 语句匹配传入的 Uri
switch (mMatcher.match(uri)) {
// 如果是 URI_NOTE表示删除笔记
case URI_NOTE:
// 将选定条件和 "ID>0" 结合起来
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
// 执行删除操作,并返回受影响的行数
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
// 如果是 URI_NOTE_ITEM表示删除单个笔记
case URI_NOTE_ITEM:
// 从 Uri 中获取笔记的 ID
id = uri.getPathSegments().get(1);
// 如果笔记 ID 小于等于 0则不允许删除
long noteId = Long.valueOf(id);
if (noteId <= 0) {
break;
}
// 执行删除操作,并返回受影响的行数
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
// 如果是 URI_DATA表示删除数据
case URI_DATA:
// 执行删除操作,并返回受影响的行数
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
// 如果是 URI_DATA_ITEM表示删除单个数据
case URI_DATA_ITEM:
// 从 Uri 中获取数据的 ID
id = uri.getPathSegments().get(1);
// 执行删除操作,并返回受影响的行数
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
// 如果匹配失败,则抛出异常
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果受影响的行数大于 0
if (count > 0) {
// 如果删除了数据,则通知数据发生了变化
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知 Uri 发生了变化
getContext().getContentResolver().notifyChange(uri, null);
}
// 返回受影响的行数
return count;
}
/******/
@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;
// 根据 Uri 进行分支处理
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 更新笔记版本号
increaseNoteVersion(-1, selection, selectionArgs);
// 更新笔记数据
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 获取 Uri 中的 id
id = uri.getPathSegments().get(1);
// 更新笔记版本号
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
// 根据 id 更新笔记数据
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 更新数据表
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
// 获取 Uri 中的 id
id = uri.getPathSegments().get(1);
// 根据 id 更新数据表
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
// 如果不匹配任何 Uri抛出异常
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果更新成功,发出通知
if (count > 0) {
if (updateData) {
// 更新数据表时,通知笔记 Uri
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知当前 Uri
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
/*****/
private String parseSelection(String selection) {
// 如果查询条件不为空,返回 AND (查询条件)
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
// 构建 SQL 语句
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
// 如果传入的 ID 或者查询条件不为空,则在 SQL 语句中添加 WHERE 子句
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
// 如果传入的 ID 不为空,添加 ID 条件
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);
}
// 执行 SQL 语句,增加笔记版本号
mHelper.getWritableDatabase().execSQL(sql.toString());
}
@Override
public String getType(Uri uri) {
// 返回 null
return null;
}
}

@ -0,0 +1,96 @@
/**
MetaDataTaskGTask
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();
// 关联的gid
private String mRelatedGid = null;
/**
* gid
*
* @param gid gid
* @param metaInfo
*/
public void setMeta(String gid, JSONObject metaInfo) {
try {
// 将关联的gid放入JSONObject中
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
// 将元数据的相关信息转换为字符串设置为Task的notes
setNotes(metaInfo.toString());
// 设置Task的名称为元数据的名称
setName(GTaskStringUtils.META_NOTE_NAME);
}
public String getRelatedGid() {
return mRelatedGid;
}
/**
*
* @return truefalse
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
/**
* JSON
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
// 解析备注信息
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
/**
* JSON
* @param js JSON
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
// 不应该被调用的方法
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
/**
*
* @return
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
/******/
/* Cursor
IllegalAccessError "MetaData:getSyncAction should not be called" "MetaData:getSyncAction 不应该被调用"*/
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -0,0 +1,107 @@
/*
* 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;
//表示节点的全局唯一标识符global identifier
private String mName;
//表示节点的名称
private long mLastModified;
//表示节点最后一次修改时间
private boolean mDeleted;
//表示节点是否已被删除
// 抽象方法,获取创建操作的 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);
// 设置节点 ID
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;
}
// 获取节点 ID
public String getGid() {
return this.mGid;
}
// 获取节点名称
public String getName() {
return this.mName;
}
// 获取最后修改时间
public long getLastModified() {
return this.mLastModified;
}
// 获取是否已删除
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -0,0 +1,191 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName(); // TAG常量用于在日志中标记这个类
private static final int INVALID_ID = -99999; // 无效ID常量
// PROJECTION_DATA常量数组用于查询数据表中的字段
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
// 数据表字段的索引常量,用于读取查询结果集
public static final int DATA_ID_COLUMN = 0;
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; // ContentResolver对象用于操作数据表
private boolean mIsCreate; // 是否创建了数据
private long mDataId; // 数据ID
private String mDataMimeType; // 数据类型
private String mDataContent; // 数据内容
private long mDataContentData1; // 数据字段1
private String mDataContentData3; // 数据字段3
private ContentValues mDiffDataValues; // ContentValues对象用于更新数据表的内容
// 构造函数初始化ContentResolver对象
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
// 从Cursor对象中读取数据
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN); // 读取数据ID
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); // 读取数据类型
mDataContent = c.getString(DATA_CONTENT_COLUMN); // 读取数据内容
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); // 读取数据字段1
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 读取数据字段3
}
// 设置数据内容
public void setContent(JSONObject js) throws JSONException {
// 从JSONObject对象中读取数据若不存在则使用默认值
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
// 比较新数据和旧数据如果不同则添加到ContentValues对象中
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
public JSONObject getContent() throws JSONException {
// 如果数据还没有在数据库中创建则返回null并打印错误日志
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
// 将数据内容转换为JSONObject对象并返回
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
public void commit(long noteId, boolean validateVersion, long version) {
// 如果数据还没有在数据库中创建,则将其插入到数据库中
if (mIsCreate) {
// 如果数据id无效且存在于差异数据值中则从差异数据值中删除该id
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
// 将数据与所属笔记的id关联起来并将其插入到数据库中
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
// 从uri中获取新插入数据的id
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
// 如果数据已存在于数据库中,则将其更新
if (mDiffDataValues.size() > 0) {
int result = 0;
// 如果需要验证版本号,则只有当笔记的版本号与指定版本号相同时,才能更新数据
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
// 如果更新的数据数量为0则打印警告日志
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
// 清空差异数据值并将是否创建标志设为false
mDiffDataValues.clear();
mIsCreate = false;
}
//返回数据索引
public long getId() {
return mDataId;
}
}

@ -0,0 +1,551 @@
/*
* 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
};
// 定义一个公共静态常量PROJECTION_NOTE表示需要查询的列名数组
public static final int ID_COLUMN = 0;
// 定义一个公共静态常量ID_COLUMN表示查询结果集合中id列的下标
public static final int ALERTED_DATE_COLUMN = 1;
// 定义一个公共静态常量ALERTED_DATE_COLUMN表示查询结果集合中alerted_date列的下标
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;
// 定义一个私有成员变量mDiffNoteValues表示当前对象的差异值
private ArrayList<SqlData> mDataList;
// 定义一个私有成员变量mDataList表示当前对象的数据列表
public SqlNote(Context context) {
mContext = context; // 保存上下文对象
mContentResolver = context.getContentResolver(); // 获取ContentResolver对象
mIsCreate = true; // 标记对象是否新建
mId = INVALID_ID; // 设置对象的id为无效值
mAlertDate = 0; // 设置提醒日期为0
mBgColorId = ResourceParser.getDefaultBgId(context); // 设置默认背景颜色
mCreatedDate = System.currentTimeMillis(); // 获取当前系统时间作为创建日期
mHasAttachment = 0; // 设置对象是否有附件为0没有
mModifiedDate = System.currentTimeMillis(); // 获取当前系统时间作为修改日期
mParentId = 0; // 设置对象的父id为0
mSnippet = ""; // 设置摘要为空字符串
mType = Notes.TYPE_NOTE; // 设置对象类型为笔记
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // 设置小部件id为无效值
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 设置小部件类型为无效值
mOriginParent = 0; // 设置对象的原始父id为0
mVersion = 0; // 设置对象版本号为0
mDiffNoteValues = new ContentValues(); // 初始化对象值差异
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
}
// SqlNote类的构造函数用于从Cursor对象中加载数据创建SqlNote对象
public SqlNote(Context context, Cursor c) {
mContext = context; // 保存上下文对象
mContentResolver = context.getContentResolver(); // 获取ContentResolver对象
mIsCreate = false; // 标记对象不是新建
loadFromCursor(c); // 从Cursor对象中加载数据
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE) // 如果对象是笔记类型
loadDataContent(); // 加载笔记数据内容
mDiffNoteValues = new ContentValues(); // 初始化对象值差异
}
// SqlNote类的构造函数用于从id值中加载数据创建SqlNote对象
public SqlNote(Context context, long id) {
mContext = context; // 保存上下文对象
mContentResolver = context.getContentResolver(); // 获取ContentResolver对象
mIsCreate = false; // 标记对象不是新建
loadFromCursor(id); // 从id值中加载数据
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE) // 如果对象是笔记类型
loadDataContent(); // 加载笔记数据内容
mDiffNoteValues = new ContentValues(); // 初始化对象值差异
}
// 根据 id 从数据库中查询便签数据并加载
private void loadFromCursor(long id) {
Cursor c = null; // 声明一个 Cursor 变量并初始化为 null
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null); // 通过内容提供器获取便签的 Cursor
if (c != null) {
c.moveToNext(); // 将 Cursor 移到下一行,因为默认情况下它指向的是查询结果的第一行之前的位置
loadFromCursor(c); // 加载数据到 SqlNote 的成员变量中
} else {
Log.w(TAG, "loadFromCursor: cursor = null"); // 输出警告日志
}
} finally {
if (c != null)
c.close();
}
}
// 从 Cursor 中加载便签数据
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN); // 获取便签的 id
mAlertDate = c.getLong(ALERTED_DATE_COLUMN); // 获取提醒时间
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); // 获取背景颜色 ID
mCreatedDate = c.getLong(CREATED_DATE_COLUMN); // 获取创建时间
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); // 获取是否有附件
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); // 获取修改时间
mParentId = c.getLong(PARENT_ID_COLUMN); // 获取父级便签的 id
mSnippet = c.getString(SNIPPET_COLUMN); // 获取便签内容的摘要信息
mType = c.getInt(TYPE_COLUMN); // 获取便签类型
mWidgetId = c.getInt(WIDGET_ID_COLUMN); // 获取小部件的 id
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); // 获取小部件类型
mVersion = c.getLong(VERSION_COLUMN); // 获取版本号
}
private void loadDataContent() {
Cursor c = null; //定义游标c
mDataList.clear(); //清空数据列表mDataList
try {
//查询内容提供器获取数据条件为note_id=mId即查询当前笔记的数据内容
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) { //如果游标c不为空
if (c.getCount() == 0) { //如果游标c的数量为0则表示当前笔记无数据内容
Log.w(TAG, "it seems that the note has not data"); //输出警告信息
return; //返回
}
while (c.moveToNext()) { //如果游标c有下一行记录则遍历游标c
SqlData data = new SqlData(mContext, c); //新建SqlData对象data传入mContext和游标c
mDataList.add(data); //将data加入到数据列表mDataList中
}
} else {
Log.w(TAG, "loadDataContent: cursor = null"); //输出警告信息
}
} finally {
//无论如何都执行finally块中的代码
if (c != null)
c.close();
}
}
public boolean setContent(JSONObject js) {
try {
// 获取note对象
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) {
// 可以更新snippet和类型
String snippet = note.has(NoteColumns.SNIPPET) ? note.getString(NoteColumns.SNIPPET) : "";
// 如果是新建笔记或者snippet改变则将新值放入mDiffNoteValues中
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, 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;
// 获取背景颜色id
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;
// 获取笔记的小部件 ID
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;
}
/**
JSONObject
@return JSONObjectnull
*/
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
// 如果该笔记未在数据库中创建则返回null
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
// 构造笔记的JSONObject对象
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对象
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) {
// 如果该笔记为文件夹或系统笔记则构造简单的笔记JSONObject对象
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) {
// 如果当前的 Note 是新建的
// 如果当前 Note 的 ID 是无效的,并且存在 ID 字段,则将其从 DiffNoteValues 中移除
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
// 将当前 Note 插入到数据库中,并从返回的 URI 中获取 Note 的 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");
}
// 如果获取到的 Note 的 ID 为 0则抛出异常
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}
// 如果当前 Note 是普通笔记,则将其关联的 SqlData 数据写入到数据库中
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
}
}
} else { // 如果当前的 Note 不是新建的
// 如果当前 Note 的 ID 无效,则抛出异常
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
// 如果当前 Note 的 DiffNoteValues 中存在修改过的值,则将其更新到数据库中
if (mDiffNoteValues.size() > 0) {
mVersion ++; // 更新 Note 的版本号
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");
}
}
// 如果当前 Note 是普通笔记,则将其关联的 SqlData 数据写入到数据库中
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);
}
}
}
// 刷新当前 Note 对象的本地数据,并将其 DiffNoteValues 清空
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues.clear();
mIsCreate = false;
}
}

@ -0,0 +1,394 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName();
private boolean mCompleted;
private String mNotes;
private JSONObject mMetaInfo;
private Task mPriorSibling;
private TaskList mParent;
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置创建操作的ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置待办事项在父任务中的索引位置
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// 设置待办事项任务的entity_delta对象
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置待办事项任务的名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 设置待办事项任务的创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 设置待办事项任务的entity类型为"task"
if (getNotes() != null) { // 如果待办事项任务有备注信息则添加到entity_delta对象中
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());
// 设置父任务类型为"group"
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 设置待办事项任务所属的列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// 如果待办事项任务有前一个同级任务则设置prior_sibling_id为前一个同级任务的ID
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印异常信息
e.printStackTrace(); // 打印堆栈跟踪信息
throw new ActionFailureException("fail to generate task-create jsonobject"); // 抛出创建操作失败的异常
}
// 返回待办事项任务的创建操作的JSONObject对象
return js;
}
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); // 将实体差异添加到操作JSONObject对象中
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印异常信息
e.printStackTrace(); // 打印堆栈跟踪信息
throw new ActionFailureException("fail to generate task-update jsonobject"); // 抛出更新操作失败的异常
}
// 返回待办事项任务的更新操作的JSONObject对象
return js;
}
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
// 设置任务id
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); // 设置任务最后修改时间
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); // 设置任务名称
}
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); // 设置任务备注信息
}
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); // 设置任务是否被删除
}
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); // 设置任务是否已完成
}
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印异常信息
e.printStackTrace(); // 打印异常堆栈信息
throw new ActionFailureException("fail to get task content from jsonobject"); // 抛出操作失败异常
}
}
}
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");// 打印警告日志
}
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
Log.e(TAG, "invalid type");
return;
}
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT));// 设置任务名称
break;
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
// 从网络创建新任务
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
// 同步任务
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());// 更新任务名称
break;
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
/**
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
}
}
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) {
Log.w(TAG, "笔记元信息已被删除");
return SYNC_ACTION_UPDATE_REMOTE; // 本地笔记元信息已被删除,需要更新到远程
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "远程笔记ID已被删除");
return SYNC_ACTION_UPDATE_LOCAL; // 远程笔记ID已被删除需要更新到本地
}
// 验证笔记ID
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "笔记ID不匹配");
return SYNC_ACTION_UPDATE_LOCAL; // 本地笔记ID与远程笔记ID不匹配需要更新到本地
}
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; // gtask ID不匹配错误
}
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 00 true false
*/
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
/**
@param completed
*/
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
/**
@param notes
*/
public void setNotes(String notes) {
this.mNotes = notes;
}
/**
@param priorSibling
*/
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
/**
@param parent
*/
public void setParent(TaskList parent) {
this.mParent = parent;
}
/**
@return
*/
public boolean getCompleted() {
return this.mCompleted;
}
/**
@return
*/
public String getNotes() {
return this.mNotes;
}
/**
@return
*/
public Task getPriorSibling() {
return this.mPriorSibling;
}
/**
@return
*/
public TaskList getParent() {
return this.mParent;
}
}

@ -0,0 +1,403 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class TaskList extends Node {
// 定义TaskList类继承自GTask类
private static final String TAG = TaskList.class.getSimpleName(); // 定义TAG常量用于日志输出
private int mIndex; // 定义mIndex变量表示任务列表的序号
private ArrayList<Task> mChildren; // 定义mChildren变量表示该任务列表包含的子任务列表
// TaskList类的构造函数初始化mChildren变量为一个空的Task列表mIndex为1
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
}
// 返回一个用于创建该任务列表的JSON对象
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); // 设置动作类型为创建任务列表
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置动作ID
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); // 设置任务列表的序号
// entity_delta
JSONObject entity = new JSONObject(); // 创建一个新的JSON对象用于存储任务列表信息
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); // 设置任务列表的类型为GROUP
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将entity对象添加到js对象中
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject"); // 抛出异常
}
return js; // 返回创建的JSON对象
}
// 返回一个用于更新该任务列表的JSON对象
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); // 设置动作类型为更新任务列表
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置动作ID
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); // 设置任务列表的ID
// entity_delta
JSONObject entity = new JSONObject(); // 创建一个新的JSON对象用于存储任务列表信息
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置任务列表的名称
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 设置任务列表是否被删除的状态
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将entity对象添加到js对象中
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject"); // 抛出异常
}
return js; // 返回创建的JSON对象
}
/**
JSON
@param js JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// 设置 ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 设置最后修改时间
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, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
}
}
/**
JSON
@param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
// 设置内容通过本地JSON对象
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
// 如果JSON对象为空或者没有指定键值对记录警告日志并退出方法
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
// 尝试解析JSON对象获取指定键的值
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// 如果获取到的值为"文件夹"类型
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
// 如果获取到的值为"系统"类型
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE);
else
// 如果获取到的值为无效类型
Log.e(TAG, "invalid system folder");
} else {
// 如果获取到的值为错误类型
Log.e(TAG, "error type");
}
} catch (JSONException e) {
// 如果出现解析异常,记录错误日志并输出异常栈
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
public JSONObject getLocalJSONFromContent() {
try {
// 尝试创建一个新的JSON对象
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
folder.put(NoteColumns.SNIPPET, folderName);
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js;
} catch (JSONException e) {
// 如果出现解析异常,记录错误日志并输出异常栈
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 如果本地没有修改
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())) {
// 如果gtask id不匹配记录错误日志并返回错误状态
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
// 获取子任务的数量
public int getChildTaskCount() {
return mChildren.size();
}
// 添加一个子任务,返回添加是否成功
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task);
if (ret) {
// 设置前一个兄弟任务和父任务
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
}
}
return ret;
}
// 在指定位置添加一个子任务,返回添加是否成功
public boolean addChildTask(Task task, int index) {
// 如果指定的位置超出了子任务的范围,返回失败
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
return false;
}
// 查找子任务列表中是否已经存在这个子任务,如果不存在则添加到指定位置
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task);
// 更新任务列表中前一个和后一个任务的信息
Task preTask = null;
Task afterTask = null;
if (index != 0)
preTask = mChildren.get(index - 1);
if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1);
task.setPriorSibling(preTask);
if (afterTask != null)
afterTask.setPriorSibling(task);
}
return true;
}
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
if (index != -1) {
ret = mChildren.remove(task);
if (ret) {
// 重置前一个兄弟和父节点
task.setPriorSibling(null);
task.setParent(null);
// 更新任务列表
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
}
}
}
return ret;
}
/**
@param task
@param index
@return truefalse
*/
//将任务移动到给定的索引位置
public boolean moveChildTask(Task task, int index) {
//如果索引无效则返回false并记录错误日志
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
}
//获取任务在列表中的位置
int pos = mChildren.indexOf(task);
//如果任务不在列表中则返回false并记录错误日志
if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list");
return false;
}
//如果任务已经在指定位置则返回true
if (pos == index)
return true;
//否则从原位置删除该任务并将其插入新位置,返回插入结果
return (removeChildTask(task) && addChildTask(task, index));
}
//根据给定的gid查找并返回子任务
public Task findChildTaskByGid(String gid) {
//遍历子任务列表查找并返回与给定gid匹配的子任务
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
if (t.getGid().equals(gid)) {
return t;
}
}
//如果没有找到则返回null
return null;
}
//返回给定任务在子任务列表中的索引,如果找不到则返回-1
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
//根据给定的索引返回子任务如果索引无效则返回null
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
return null;
}
return mChildren.get(index);
}
//根据给定的gid返回子任务如果找不到则返回null
public Task getChilTaskByGid(String gid) {
//遍历子任务列表查找并返回与给定gid匹配的子任务
for (Task task : mChildren) {
if (task.getGid().equals(gid))
return task;
}
//如果没有找到则返回null
return null;
}
//返回子任务列表
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
}
//设置任务的索引值
public void setIndex(int index) {
this.mIndex = index;
}
//返回任务的索引值
public int getIndex() {
return this.mIndex;
}
}

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

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

@ -0,0 +1,137 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义 GTASK_SYNC_NOTIFICATION_ID 常量,用于通知的 id
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义 OnCompleteListener 接口,用于在任务完成时回调
public interface OnCompleteListener {
void onComplete();
}
// 上下文对象
private Context mContext;
// 通知管理器对象
private NotificationManager mNotifiManager;
// GTask 管理器对象
private GTaskManager mTaskManager;
// 任务完成回调接口对象
private OnCompleteListener mOnCompleteListener;
// 构造方法
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
// 获取通知管理器对象
mNotifiManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
// 获取 GTask 管理器对象
mTaskManager = GTaskManager.getInstance();
}
// 取消同步
public void cancelSync() {
mTaskManager.cancelSync();
}
// 发布进度更新
public void publishProgess(String message) {
// 调用 AsyncTask 内置方法 publishProgress
publishProgress(new String[] {message});
}
// 显示通知
private void showNotification(int tickerId, String content) {
// 创建通知对象
Notification notification = new Notification(R.drawable.notification, mContext.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent;
// 根据 tickerId 不同设置不同的 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);
}
// 设置通知的 pendingIntent 和内容
notification.contentIntent = pendingIntent;
// 发送通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
// 在后台运行的方法,需要继承 AsyncTask 类并实现该方法
@Override
protected Integer doInBackground(Void... unused) {
// 发布进度更新
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity.getSyncAccountName(mContext)));
// 调用 GTaskManager 的 sync 方法同步数据
return mTaskManager.sync(mContext, this);
}
// 在进度更新时回调
@Override
protected void onProgressUpdate(String... progress) {
// 显示通知
showNotification(R.string.ticker_syncing, progress[0]);
// 如果当前上下文对象是 GTaskSyncService 类型,则发送广播
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
// 在任务执行完成时回调
@Override
protected void onPostExecute(Integer result) {
// 根据返回值显示不同的通知
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString(R.string.success_sync_account, mTaskManager.getSyncAccount()));
// 更新上次同步时间
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext.getString(R.string.error_sync_cancelled));
}
// 如果任务完成回调接口对象不为空,则新建线程执行回调方法
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -0,0 +1,737 @@
/*
* 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;
/**
* GTaskClientGoogle
*/
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName();
// Google任务的URL地址
private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 获取任务的URL地址
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 提交任务的URL地址
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// GTaskClient实例使用单例模式
private static GTaskClient mInstance = null;
// HTTP客户端
private DefaultHttpClient mHttpClient;
// 获取任务的URL地址
private String mGetUrl;
// 提交任务的URL地址
private String mPostUrl;
// 客户端版本号
private long mClientVersion;
// 是否已登录
private boolean mLoggedin;
// 上次登录时间
private long mLastLoginTime;
// 操作ID
private int mActionId;
// 用户账号
private Account mAccount;
// 更新的任务列表
private JSONArray mUpdateArray;
/**
*
*/
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
/**
* GTaskClient使
*/
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
/**
* Google
* @param activity Activity
*/
public boolean login(Activity activity) {
// 假设Cookie会在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;
}
// 如果是自定义域名则使用特定URL登录
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;
}
}
// 尝试使用Google官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true;
return true;
}
/**
* Google authToken
* @param activity Activity
* @param invalidateToken 使 token
* @return authToken null
*/
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, "there is no available google account");
return null;
}
// 从设置中获取同名账号
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
break;
}
}
if (account != null) {
mAccount = account;
} else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 获取 token
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// 如果需要使 token 失效,则调用自身再次获取 token
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken;
}
/**
* Gtask
* @param activity Activity
* @param authToken token
* @return
*/
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// 如果 token 失效,尝试重新获取 token 再次登录
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true;
}
/**
* Gtask
* @param authToken token
* @return
*/
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// 登录 Gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// 获取 cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// 获取客户端版本号
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
// 捕获所有异常
Log.e(TAG, "httpget gtask_url failed");
return false;
}
return true;
}
// 获取操作ID
private int getActionId() {
return mActionId++;
}
// 创建HTTP POST请求
private HttpPost createHttpPost() {
// 创建HttpPost对象传入POST请求的URL
HttpPost httpPost = new HttpPost(mPostUrl);
// 设置请求头信息
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1");
return httpPost;
}
// 获取响应内容
private String getResponseContent(HttpEntity entity) throws IOException {
// 初始化内容编码为null
String contentEncoding = null;
// 如果实体的内容编码不为空,则将内容编码设置为实体的内容编码
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
// 获取响应内容的输入流
InputStream input = entity.getContent();
// 如果内容编码为gzip则使用GZIPInputStream解压缩输入流
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
}
// 如果内容编码为deflate则使用InflaterInputStream解压缩输入流
else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
}
try {
// 将输入流转换成字符流
InputStreamReader isr = new InputStreamReader(input);
// 使用缓冲读取器从字符流中读取响应内容
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
while (true) {
String buff = br.readLine();
if (buff == null) {
// 如果读取完毕,则返回响应内容的字符串
return sb.toString();
}
// 将读取到的内容添加到字符串构建器中
sb = sb.append(buff);
}
} finally {
// 关闭输入流
input.close();
}
}
// 发送POST请求并获取响应内容
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
// 如果没有登录,则抛出异常
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
// 创建HttpPost对象
HttpPost httpPost = createHttpPost();
try {
// 创建参数列表
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
// 将传入的JSONObject对象转换成字符串并添加到参数列表中
list.add(new BasicNameValuePair("r", js.toString()));
// 创建UrlEncodedFormEntity对象并指定字符编码为UTF-8
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
// 将UrlEncodedFormEntity对象设置为HttpPost对象的实体
httpPost.setEntity(entity);
// 执行POST请求并获取响应对象
HttpResponse response = mHttpClient.execute(httpPost);
// 获取响应内容并将其转换成JSONObject对象
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
} catch (ClientProtocolException e) {
// 发送请求失败,抛出异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (IOException e) {
// 发送请求失败,抛出异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (JSONException e) {
// 响应内容无法转换为JSONObject对象抛出异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("unable to convert response content to jsonobject");
} catch (Exception e) {
// 发送请求失败,抛出异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request");
}
}
/******/
/**
*
* @param task
* @throws NetworkFailureException
*/
public void createTask(Task task) throws NetworkFailureException {
// 提交更新
commitUpdate();
try {
// 创建JSONObject对象
JSONObject jsPost = new JSONObject();
// 创建JSONArray对象
JSONArray actionList = new JSONArray();
// 将新建任务的操作添加到actionList中
actionList.put(task.getCreateAction(getActionId()));
// 将actionList添加到JSONObject中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 将客户端版本号添加到JSONObject中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送post请求并获取响应数据
JSONObject jsResponse = postRequest(jsPost);
// 获取响应结果中的第一个JSONObject对象
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 将新建任务的gid设置为返回结果中的new_id
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
// 记录异常日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出操作失败异常
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
/**
*
* @param tasklist
* @throws NetworkFailureException
*/
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
// 提交更新
commitUpdate();
try {
// 创建JSONObject对象
JSONObject jsPost = new JSONObject();
// 创建JSONArray对象
JSONArray actionList = new JSONArray();
// 将新建任务列表的操作添加到actionList中
actionList.put(tasklist.getCreateAction(getActionId()));
// 将actionList添加到JSONObject中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 将客户端版本号添加到JSONObject中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送post请求并获取响应数据
JSONObject jsResponse = postRequest(jsPost);
// 获取响应结果中的第一个JSONObject对象
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 将新建任务列表的gid设置为返回结果中的new_id
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
// 记录异常日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出操作失败异常
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
/**
*
* @throws NetworkFailureException
*/
public void commitUpdate() throws NetworkFailureException {
// 如果有更新操作
if (mUpdateArray != null) {
try {
// 创建JSONObject对象
JSONObject jsPost = new JSONObject();
// 将更新操作添加到actionList中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// 将客户端版本号添加到JSONObject中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送post请求
postRequest(jsPost);
// 更新操作数组置空
mUpdateArray = null;
} catch (JSONException e) {
// 记录异常日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出操作失败异常
throw new ActionFailureException("commit update: handing jsonobject failed");
}
}
}
/**
*
* @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(); // 创建一个新的JSONArray
mUpdateArray.put(node.getUpdateAction(getActionId())); // 将节点的更新操作添加到JSONArray中
}
}
/**
*
* @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(); // 创建一个新的JSONObject
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); // 设置操作类型为移动
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置操作ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 设置任务ID
if (preParent == curParent && task.getPriorSibling() != null) {
// 如果在同一个任务列表中移动且不是第一个则设置prioring_sibing_id
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 设置移动前的任务列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 设置移动后的父任务列表ID
if (preParent != curParent) {
// 如果在不同的任务列表中移动则设置dest_list
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action); // 将操作添加到操作列表中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本号
postRequest(jsPost); // 发送POST请求
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed"); // 抛出操作失败异常
}
}
/**
*
* @param node
* @throws NetworkFailureException
*/
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate(); // 提交更新数组
try {
JSONObject jsPost = new JSONObject(); // 创建一个新的JSONObject
JSONArray actionList = new JSONArray();
// action_list
node.setDeleted(true); // 设置节点为已删除
actionList.put(node.getUpdateAction(getActionId())); // 将节点的更新操作添加到操作列表中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本号
postRequest(jsPost); // 发送POST请求
mUpdateArray = null; // 将更新数组设置为null
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("delete node: handing jsonobject failed"); // 抛出操作失败异常
}
}
/**
*
*
* @return JSONArray
* @throws NetworkFailureException
*/
public JSONArray getTaskLists() throws NetworkFailureException {
// 如果未登录,则抛出异常
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
try {
// 创建HttpGet对象用于向服务器发送GET请求
HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// 获取任务列表
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) {
// 如果出现ClientProtocolException则抛出网络连接异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (IOException e) {
// 如果出现IOException则抛出网络连接异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (JSONException e) {
// 如果出现JSONException则抛出操作失败异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
/**
* ID
*
* @param listGid ID
* @return JSONArray
* @throws NetworkFailureException
*/
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
try {
// 创建JSONObject对象设置请求参数
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);
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);
// 发送POST请求获取响应JSONObject对象
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) {
// 如果出现JSONException则抛出操作失败异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task list: handing jsonobject failed");
}
}
/**
*
*
* @return
*/
public Account getSyncAccount() {
return mAccount;
}
/**
*
*/
public void resetUpdateArray() {
mUpdateArray = null;
}
}

@ -0,0 +1,885 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
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; // 活动的Activity实例
private Context mContext; // 上下文
private ContentResolver mContentResolver; // ContentResolver实例
private boolean mSyncing; // 是否正在同步
private boolean mCancelled; // 是否已取消同步
private HashMap<String, TaskList> mGTaskListHashMap; // 任务列表哈希表
private HashMap<String, Node> mGTaskHashMap; // 任务哈希表
private HashMap<String, MetaData> mMetaHashMap; // 元数据哈希表
private TaskList mMetaList; // 元数据列表
private HashSet<Long> mLocalDeleteIdMap; // 本地删除ID哈希表
private HashMap<String, Long> mGidToNid; // GID到NID的映射哈希表
private HashMap<Long, String> mNidToGid; // NID到GID的映射哈希表
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>(); // 初始化本地删除ID哈希表为空
mGidToNid = new HashMap<String, Long>(); // 初始化GID到NID的映射哈希表为空
mNidToGid = new HashMap<Long, String>(); // 初始化NID到GID的映射哈希表为空
}
public static synchronized GTaskManager getInstance() { // 获取单例实例
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
public synchronized void setActivityContext(Activity activity) { // 设置活动的Activity实例
// 用于获取认证令牌
mActivity = activity;
}
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) { // 如果正在同步中,返回同步进行中状态码
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
mContext = context; // 设置上下文
mContentResolver = mContext.getContentResolver();
mSyncing = true; // 设置正在同步
mCancelled = false; // 设置未取消同步
mGTaskListHashMap.clear(); // 清除任务列表哈希表
mGTaskHashMap.clear(); // 清除任务哈希表
mMetaHashMap.clear(); // 清除元数据哈希表
mLocalDeleteIdMap.clear(); // 清除本地删除ID哈希表
mGidToNid.clear(); // 清除GID到NID的映射哈希表
mNidToGid.clear(); // 清除NID到GID的映射哈希表
try {
GTaskClient client = GTaskClient.getInstance(); // 获取GTaskClient实例
client.resetUpdateArray(); // 重置更新数组
// 登录Google任务
if (!mCancelled) {
if (!client.login(mActivity)) { // 如果登录失败抛出NetworkFailureException
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 { // 在finally中清除哈希表并设置同步完成
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 如果已取消同步,返回取消同步状态码,否则返回成功状态码
}
/**
* GTaskList NetworkFailureException
*/
private void initGTaskList() throws NetworkFailureException {
// 如果已经取消则直接返回
if (mCancelled)
return;
// 获取 GTaskClient 的实例
GTaskClient client = GTaskClient.getInstance();
try {
// 获取任务列表
JSONArray jsTaskLists = client.getTaskLists();
// 首先初始化 Meta list
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);
// 如果名称为 GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META则初始化 Meta list
if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// 加载 Meta 数据
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);
}
}
}
}
}
// 如果 Meta list 不存在,则创建一个新的 Meta list
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);
// 如果名称以 GTaskStringUtils.MIUI_FOLDER_PREFFIX 开头且不是 Meta list则初始化任务列表
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) {
// 如果处理 JSONObject 失败,则抛出 ActionFailureException 异常
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; // GTasks ID
Node node; // 节点
mLocalDeleteIdMap.clear(); // 清空本地删除ID映射表
if (mCancelled) { // 如果已经取消了同步,直接返回
return;
}
// 处理本地已删除的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行删除远程同步操作
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加到本地删除ID映射表
}
} else {
Log.w(TAG, "failed to query trash folder"); // 查询回收站失败
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 同步文件夹
syncFolder();
// 处理数据库中已存在的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c); // 获取同步类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地新增
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c); // 执行同步操作
}
} else {
Log.w(TAG, "failed to query existing note in database"); // 查询已存在的笔记失败
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理剩余的节点
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行本地新增同步操作
}
// 清空本地删除表
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { // 批量删除本地已删除笔记
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// 刷新本地同步ID
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate(); // 提交更新
refreshLocalSyncId(); // 刷新本地同步ID
}
}
/**
*
* @throws NetworkFailureException
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null; // 游标对象,用于查询数据库
String gid; // Google Tasks ID
Node node; // Google Tasks 中的节点
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) { // Google Tasks 中存在该节点
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 { // Google Tasks 中不存在该节点
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) { // Google Tasks 中存在该节点
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 { // Google Tasks 中不存在该节点
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) { // Google Tasks 中存在该节点
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(); // 迭代 mGTaskListHashMap 中的每一个元素
while (iter.hasNext()) { // 如果还有下一个元素
Map.Entry<String, TaskList> entry = iter.next(); // 获取下一个元素
gid = entry.getKey(); // 获取元素的键
node = entry.getValue(); // 获取元素的值
if (mGTaskHashMap.containsKey(gid)) { // 如果 mGTaskHashMap 中包含该键
mGTaskHashMap.remove(gid); // 从 mGTaskHashMap 中删除该键值对
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行 doContentSync 方法,并传入 SYNC_ACTION_ADD_LOCAL、node 和 null 三个参数
}
}
if (!mCancelled) // 如果 mCancelled 为假
GTaskClient.getInstance().commitUpdate(); // 执行 GTaskClient.getInstance().commitUpdate() 方法
}
/**
*
*
* @param syncType
* @param node
* @param c
* @throws NetworkFailureException
*/
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("未知的同步操作类型");
}
}
/**
*
*
* @param node
* @throws NetworkFailureException
*/
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已存在于本地数据库中则需要创建一个新的id
note.remove(NoteColumns.ID);
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// 如果该id已存在于本地数据库中则需要创建一个新的id
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
// 将更新后的节点信息添加到要创建的节点对象中
sqlNote.setContent(js);
// 获取要添加的节点的父节点在本地数据库中的id
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
// 如果没有找到父节点的id则抛出异常
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
// 将父节点的id添加到要创建的节点对象中
sqlNote.setParentId(parentId.longValue());
}
// 设置要创建的节点对象的gid
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);
}
/**
*
*
* @param node
* @param c
* @throws NetworkFailureException
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
// 如果已经取消了更新操作,则直接返回
if (mCancelled) {
return;
}
SqlNote sqlNote;
// 根据游标对象获取要更新的节点的信息
sqlNote = new SqlNote(mContext, c);
// 将传入的节点信息更新到要更新的节点中
sqlNote.setContent(node.getLocalJSONFromContent());
// 根据节点类型获取要更新节点的父节点的ID
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
// 如果父节点ID为空则抛出异常
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
// 设置要更新节点的父节点ID
sqlNote.setParentId(parentId.longValue());
// 提交更新操作
sqlNote.commit(true);
// 更新远程节点的元数据信息
updateRemoteMeta(node.getGid(), sqlNote);
}
//这段Java代码实现了一个添加远程节点的方法下面我们逐行进行详细的注释说明
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
//这是一个私有方法,用于添加远程节点。它需要传入一个节点对象和一个光标对象。如果添加过程中出现网络失败,将会抛出
// NetworkFailureException 异常。
if (mCancelled) {
return;
}
//如果任务已被取消,直接返回。
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
//创建一个 SqlNote 对象和一个 Node 对象。
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
//如果 SqlNote 对象表示的是笔记类型,则创建一个 Task 对象,并使用 SqlNote 对象的 getContent() 方法获取本地 JSON 内容,
// 将其设置为 Task 对象的内容。
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);
//获取该笔记的父级任务列表的 GID如果不存在则抛出异常。然后将该任务添加到父级任务列表中。
GTaskClient.getInstance().createTask(task);
n = (Node) task;
//使用 GTaskClient 创建该任务,并将其转换为 Node 对象。
updateRemoteMeta(task.getGid(), sqlNote);
//更新远程元数据。
} else {
TaskList tasklist = null;
//如果 SqlNote 对象表示的是文件夹类型,则创建一个 TaskList 对象。
// we need to skip folder if it has already existed
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();
//获取文件夹的名称。如果是根文件夹,则使用默认名称;如果是通话记录文件夹,则使用特定的名称;否则使用 SqlNote 对象的摘录作为名称。
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
//遍历任务列表哈希表,查找是否存在同名任务列表。如果存在,则将其作为 tasklist 对象;否则 tasklist 对象为空。
// no match we can add now
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
//如果 tasklist 对象为空,则创建新的任务列表,并使用本地 JSON 内容初始化其内容。
// 然后使用 GTaskClient 创建该任务列表,并将其添加到任务列表哈希表中。然后将 tasklist 转换为 Node 对象。
}
// update local note
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
//更新本地笔记的 GID并将其标记为已提交。
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
//更新 GID 和 ID 之间的映射。
}
//这段Java代码的主要作用是更新远程云端的节点和元数据。
/**
1. `updateRemoteNode()``node``c``Cursor`
2. `mCancelled``true`
3. `Cursor``SqlNote`
4. `node``setContentByLocalJSON()`
5. `GTaskClient`
6. `updateRemoteMeta()`
7.
8.
9. `updateRemoteMeta()``gid``sqlNote``SqlNote`
10. `sqlNote``null`
11.
12. `GTaskClient`
13.
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
// 根据Cursor对象创建SqlNote对象
SqlNote sqlNote = new SqlNote(mContext, c);
// 更新节点的内容
node.setContentByLocalJSON(sqlNote.getContent());
// 将更新后的节点添加到GTaskClient任务列表中
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);
}
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任务列表中
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
* @throws NetworkFailureException
*/
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;
}
}

@ -0,0 +1,149 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
// GTaskSyncService 类,继承自 Service 类,用于同步任务
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";
// 定义静态的 GTaskASyncTask 对象
private static GTaskASyncTask mSyncTask = null;
// 定义静态的同步进度字符串
private static String mSyncProgress = "";
// 开始同步的方法
private void startSync() {
if (mSyncTask == null) {
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
public void onComplete() {
mSyncTask = null;
sendBroadcast("");
stopSelf();
}
});
sendBroadcast("");
mSyncTask.execute();
}
}
// 取消同步的方法
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 重写 onCreate 方法
@Override
public void onCreate() {
mSyncTask = null;
}
// 重写 onStartCommand 方法
@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);
}
// 重写 onLowMemory 方法
@Override
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 重写 onBind 方法
public IBinder onBind(Intent intent) {
return null;
}
// 发送广播的方法
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
sendBroadcast(intent);
}
// 开始同步的静态方法
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
// 取消同步的静态方法
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
// 判断是否正在同步的静态方法
public static boolean isSyncing() {
return mSyncTask != null;
}
// 获取进度字符串的静态方法
public static String getProgressString() {
return mSyncProgress;
}
}

@ -0,0 +1,245 @@
/*
* 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; // 声明一个 ContentValues 对象,用于存储 Note 对象的差异值
private NoteData mNoteData; // 声明一个 NoteData 对象,用于存储 Note 数据
private static final String TAG = "Note"; // 声明一个 TAG 常量,用于在日志中输出信息
/**
* 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);
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, "Get note id error :" + e.toString()); // 输出错误信息
noteId = 0;
}
if (noteId == -1) { // 如果笔记 ID 为 -1则表示创建笔记失败抛出异常
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId; // 返回新创建的笔记 ID
}
public Note() { // Note 类的构造函数
mNoteDiffValues = new ContentValues(); // 初始化 ContentValues 对象
mNoteData = new NoteData(); // 初始化 NoteData 对象
}
public void setNoteValue(String key, String value) { // 设置笔记值的方法
mNoteDiffValues.put(key, value); // 存储键值对到 ContentValues 对象
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 将 LOCAL_MODIFIED 标志设置为 1
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 将笔记的修改时间设置为当前时间
}
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value); // 设置笔记的文本数据
}
public void setTextDataId(long id) {
mNoteData.setTextDataId(id); // 设置笔记的文本数据ID
}
public long getTextDataId() {
return mNoteData.mTextDataId; // 获取笔记的文本数据ID
}
public void setCallDataId(long id) {
mNoteData.setCallDataId(id); // 设置笔记的电话数据ID
}
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value); // 设置笔记的电话数据
}
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
// 检查笔记是否被本地修改过
}
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId); // 如果笔记ID非法则抛出异常
}
if (!isLocalModified()) {
return true; // 如果笔记没有被本地修改过,则直接返回 true
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen"); // 更新笔记失败,记录错误日志
// Do not return, fall through
}
mNoteDiffValues.clear(); // 清空笔记的差异值
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false; // 如果笔记的数据被修改且同步到 ContentResolver 失败,则返回 false
}
return true; // 成功同步笔记数据,则返回 true
}
private class NoteData {
private long mTextDataId; // 文本数据的 ID
private ContentValues mTextDataValues; // 存储文本数据的 ContentValues
private long mCallDataId; // 通话数据的 ID
private ContentValues mCallDataValues; // 存储通话数据的 ContentValues
private static final String TAG = "NoteData"; // 日志标签
public NoteData() { // 构造函数
mTextDataValues = new ContentValues(); // 初始化文本数据的 ContentValues
mCallDataValues = new ContentValues(); // 初始化通话数据的 ContentValues
mTextDataId = 0; // 初始化文本数据的 ID
mCallDataId = 0; // 初始化通话数据的 ID
}
boolean isLocalModified() { // 判断是否有本地修改
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
void setTextDataId(long id) { // 设置文本数据的 ID
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
void setCallDataId(long id) { // 设置通话数据的 ID
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
}
void setCallData(String key, String value) { // 设置通话数据的键值对
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
void setTextData(String key, String value) { // 设置文本数据的键值对
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
Uri pushIntoContentResolver(Context context, long noteId) {
/*
*/
if (noteId <= 0) {
throw new IllegalArgumentException("错误的笔记ID" + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {
// 插入文本数据
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "插入新的文本数据失败笔记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);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "插入新的通话数据失败笔记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 e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
return null;
}
}
}

@ -0,0 +1,368 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote {
// Note for the working note
private Note mNote;
// Note Id
private long mNoteId;
// Note content
private String mContent;
// Note mode
private int mMode;
private long mAlertDate;
private long mModifiedDate;
private int mBgColorId;
private int mWidgetId;
private int mWidgetType;
private long mFolderId;
private Context mContext;
private static final String TAG = "WorkingNote";
private boolean mIsDeleted;
private NoteSettingChangedListener mNoteSettingStatusListener;
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3;
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
private static final int NOTE_WIDGET_ID_COLUMN = 3;
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct
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;
}
// Existing note construct
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, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with 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));
} else if (DataConstants.CALL_NOTE.equals(type)) {
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
cursor.close();
} else {
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
return note;
}
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
public boolean existInDatabase() {
return mNoteId > 0;
}
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
return true;
}
}
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
public void markDeleted(boolean mark) {
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
public String getContent() {
return mContent;
}
public long getAlertDate() {
return mAlertDate;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
public int getBgColorId() {
return mBgColorId;
}
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
public int getCheckListMode() {
return mMode;
}
public long getNoteId() {
return mNoteId;
}
public long getFolderId() {
return mFolderId;
}
public int getWidgetId() {
return mWidgetId;
}
public int getWidgetType() {
return mWidgetType;
}
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
*/
void onBackgroundColorChanged();
/**
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -0,0 +1,344 @@
/*
* 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";
// Singleton stuff
private static BackupUtils sInstance;
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
public int exportToText() {
return mTextExport.exportToText();
}
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
private final String [] TEXT_FORMAT;
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
private Context mContext;
private String mFileName;
private String mFileDirectory;
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// print a line separator between note
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
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;
}
}

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

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

@ -0,0 +1,181 @@
/*
* 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
};
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[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
};
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
public static class WidgetBgResources {
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,
};
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
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
};
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
};
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

@ -0,0 +1,158 @@
/*
* 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;
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 = 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 (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
dialog.show().setOnDismissListener(this);
}
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
break;
}
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}

@ -0,0 +1,65 @@
/*
* 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;
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();
}
}
}

@ -0,0 +1,30 @@
/*
* 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);
}
}

@ -0,0 +1,485 @@
/*
* 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;
public class DateTimePicker extends FrameLayout {
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
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) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
}
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,90 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
updateTitle(mDate.getTimeInMillis());
}
});
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
set24HourView(DateFormat.is24HourFormat(this.getContext()));
updateTitle(mDate.getTimeInMillis());
}
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

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

@ -0,0 +1,80 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
private class FolderListItem extends LinearLayout {
private TextView mName;
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
public void bind(String name) {
mName.setText(name);
}
}
}

@ -0,0 +1,878 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
private class HeadViewHolder {
public TextView tvModified;
public ImageView ivAlertIcon;
public TextView tvAlertDate;
public ImageView ibSetBgColor;
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity";
private HeadViewHolder mNoteHeaderHolder;
private View mHeadViewPanel;
private View mNoteBgColorSelector;
private View mFontSizeSelector;
private EditText mNoteEditor;
private View mNoteEditorPanel;
private WorkingNote mWorkingNote;
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
private LinearLayout mEditTextList;
private String mUserQuery;
private Pattern mPattern;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
}
/**
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
/**
* Starting from the searched result
*/
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())) {
// New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
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);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this));
// Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
if (callDate != 0 && phoneNumber != null) {
if (TextUtils.isEmpty(phoneNumber)) {
Log.w(TAG, "The call record number is null");
}
long noteId = 0;
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load call note failed with note id" + noteId);
finish();
return false;
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate);
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId);
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {
Log.e(TAG, "Intent not specified action, should not support");
finish();
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);
return true;
}
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}
private void initNoteScreen() {
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));
/**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader();
}
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis();
if (time > mWorkingNote.getAlertDate()) {
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);
} else {
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
};
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return super.dispatchTouchEvent(ev);
}
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}
return true;
}
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
};
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
}
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
}
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote();
break;
case R.id.menu_delete:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop:
sendToDesktop();
break;
case R.id.menu_alert:
setReminder();
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
default:
break;
}
return true;
}
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true);
}
});
d.show();
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}
private void createNewNote() {
// Firstly, save current editing notes
saveNote();
// For safety, start a new NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true);
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {
updateWidget();
}
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();
if (childCount == 1) {
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index);
NoteEditText edit = null;
if(index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);
edit.requestFocus();
edit.setSelection(length);
}
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);
mEditTextList.addView(view, index);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();
edit.setSelection(0);
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
private void switchToListMode(String text) {
mEditTextList.removeAllViews();
String[] items = text.split("\n");
int index = 0;
for (String item : items) {
if(!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
index++;
}
}
mEditTextList.addView(getListItem("", index));
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE);
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
});
if (item.startsWith(TAG_CHECKED)) {
cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
item = item.substring(TAG_CHECKED.length(), item.length()).trim();
} else if (item.startsWith(TAG_UNCHECKED)) {
cb.setChecked(false);
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {
Log.e(TAG, "Wrong index, should not happen");
return;
}
if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
}
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {
switchToListMode(mNoteEditor.getText().toString());
} else {
if (!getWorkingText()) {
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
""));
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
}
private boolean getWorkingText() {
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View view = mEditTextList.getChildAt(i);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
hasChecked = true;
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
}
}
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
}
return hasChecked;
}
private boolean saveNote() {
getWorkingText();
boolean saved = mWorkingNote.saveNote();
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
}
return saved;
}
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent sender = new Intent();
Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent()));
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
showToast(R.string.info_note_enter_desktop);
sendBroadcast(sender);
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");
showToast(R.string.error_note_empty_for_send_to_desktop);
}
}
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, "");
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
public void OnOpenMenu(View view) {
openOptionsMenu();
}
}

@ -0,0 +1,217 @@
/*
* 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;
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:" ;
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
public void setIndex(int index) {
mIndex = index;
}
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@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);
}
@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);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} 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);
}
@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);
}
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
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) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}
}

@ -0,0 +1,224 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
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;
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 = "";
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);
}
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");
}
}
}
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -0,0 +1,992 @@
/*
* 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;
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
private static final int MENU_FOLDER_DELETE = 0;
private static final int MENU_FOLDER_VIEW = 1;
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;
private int mOriginY;
private int mDispatchY;
private TextView mTitleBar;
private long mCurrentFolderId;
private ContentResolver mContentResolver;
private ModeCallback mModeCallBack;
private static final String TAG = "NotesListActivity";
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
private NoteItemData mFocusNoteDataItem;
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
private 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;
@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();
}
@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());
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;
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;
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);
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);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
public boolean onMenuItemClick(MenuItem item) {
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
updateMenu();
return true;
}
});
return true;
}
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// Update dropdown menu
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item != null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all);
} else {
item.setChecked(false);
item.setTitle(R.string.menu_select_all);
}
}
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.clearChoices();
mNotesListView.setItemChecked(0,true);
mNotesListView.setItemChecked(0,false);
mNotesListView.setLongClickable(true);
System.out.println("-----------------onDestroyActionMode------------------");
mNotesListAdapter.notifyDataSetChanged();
// closeOptionsMenu();
// mNotesListView.invalidate();
mAddNewNote.setVisibility(View.VISIBLE);
mMenuSet.setVisibility(View.VISIBLE);
// mNotesListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
// menu.clear();
// menu.close();
}
public void finishActionMode() {
mActionMode.finish();
mActionMode = null;
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
}
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show();
return true;
}
switch (item.getItemId()) {
case R.id.delete:
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
startQueryDestinationFolders();
break;
default:
return false;
}
return true;
}
}
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
int eventY = start + (int) event.getY();
/**
* Minus TitleBar's height
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* HACKME:When click the transparent part of "New Note" button, dispatch
* the event to the list view behind this button. The transparent part of
* "New Note" button could be expressed by formula y=-0.12x+94Unit:pixel
* and the line top of the button. The coordinate based on left of the "New
* Note" button. The 94 represents maximum height of the transparent part.
* Notice that, if the background of the button changes, the formula should
* also change. This is very bad, just for the UI designer's strong requirement.
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false;
}
};
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
mNotesListAdapter.changeCursor(cursor);
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
showFolderListMenu(cursor);
} else {
Log.e(TAG, "Query folder failed");
}
break;
default:
return;
}
}
}
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(R.string.menu_title_select_folder);
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode();
}
});
builder.show();
}
private void createNewNote() {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
private void batchDelete() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
if (!isSyncMode()) {
// if not synced, delete notes directly
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// in sync mode, we'll move the deleted note into the trash
// folder
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
mModeCallBack.finishActionMode();
}
}.execute();
}
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
if (!isSyncMode()) {
// if not synced, delete folder directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// in sync mode, we'll move the deleted folder into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
}
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery();
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mState = ListEditState.CALL_RECORD_FOLDER;
mAddNewNote.setVisibility(View.GONE);
mMenuSet.setVisibility(View.GONE);
} else {
mState = ListEditState.SUB_FOLDER;
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mTitleBar.setText(R.string.call_record_folder_name);
} else {
mTitleBar.setText(data.getSnippet());
}
mTitleBar.setVisibility(View.VISIBLE);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_new_note:
createNewNote();
break;
default:
break;
}
}
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
private void hideSoftInput(View view) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showCreateOrModifyFolderDialog(final boolean create) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
showSoftInput();
if (!create) {
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet());
builder.setTitle(getString(R.string.menu_folder_change_name));
} else {
Log.e(TAG, "The long click data item is null");
return;
}
} else {
etName.setText("");
builder.setTitle(this.getString(R.string.menu_create_folder));
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName);
}
});
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button)dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etName);
String name = etName.getText().toString();
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show();
etName.setSelection(0, etName.length());
return;
}
if (!create) {
if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] {
String.valueOf(mFocusNoteDataItem.getId())
});
}
} else if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
}
dialog.dismiss();
}
});
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
}
/**
* When the name edit text is null, disable the positive button
*/
etName.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
} else {
positive.setEnabled(true);
}
}
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
}
@Override
public void onBackPressed() {
System.out.println("-------onBackPressed---00000");
switch (mState) {
case SUB_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
startAsyncNotesListQuery();
mTitleBar.setVisibility(View.GONE);
break;
case CALL_RECORD_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
mAddNewNote.setVisibility(View.VISIBLE);
mMenuSet.setVisibility(View.VISIBLE);
mTitleBar.setVisibility(View.GONE);
startAsyncNotesListQuery();
break;
case NOTE_LIST:
System.out.println("-------onBackPressed---");
super.onBackPressed();
break;
default:
break;
}
}
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
appWidgetId
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view);
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete);
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name);
}
}
};
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
mNotesListView.setOnCreateContextMenuListener(null);
}
super.onContextMenuClosed(menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mFocusNoteDataItem == null) {
Log.e(TAG, "The long click data item is null");
return false;
}
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
openFolder(mFocusNoteDataItem);
break;
case MENU_FOLDER_DELETE:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_folder));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId());
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case MENU_FOLDER_CHANGE_NAME:
showCreateOrModifyFolderDialog(false);
break;
default:
break;
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
if (mState == ListEditState.NOTE_LIST) {
getMenuInflater().inflate(R.menu.note_list, menu);
// set sync or sync_cancel
menu.findItem(R.id.menu_sync).setTitle(
GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
} else if (mState == ListEditState.SUB_FOLDER) {
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else {
Log.e(TAG, "Wrong state:" + mState);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_folder: {
showCreateOrModifyFolderDialog(true);
break;
}
case R.id.menu_export_text: {
exportNoteToText();
break;
}
case R.id.menu_sync: {
if (isSyncMode()) {
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
GTaskSyncService.startSync(this);
} else {
GTaskSyncService.cancelSync(this);
}
} else {
startPreferenceActivity();
}
break;
}
case R.id.menu_setting: {
startPreferenceActivity();
break;
}
case R.id.menu_new_note: {
createNewNote();
break;
}
case R.id.menu_search:
onSearchRequested();
break;
default:
break;
}
return true;
}
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false);
return true;
}
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... unused) {
return backup.exportToText();
}
@Override
protected void onPostExecute(Integer result) {
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_unmounted));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SUCCESS) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.success_sdcard_export));
builder.setMessage(NotesListActivity.this.getString(
R.string.format_exported_file_location, backup
.getExportedTextFileName(), backup.getExportedTextFileDir()));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_export));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
}.execute();
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1);
}
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
if (mNotesListAdapter.isInChoiceMode()) {
if (item.getType() == Notes.TYPE_NOTE) {
position = position - mNotesListView.getHeaderViewsCount();
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
}
return;
}
switch (mState) {
case NOTE_LIST:
if (item.getType() == Notes.TYPE_FOLDER
|| item.getType() == Notes.TYPE_SYSTEM) {
openFolder(item);
} else if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
} else {
Log.e(TAG, "Wrong note type in NOTE_LIST");
}
break;
case SUB_FOLDER:
case CALL_RECORD_FOLDER:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
} else {
Log.e(TAG, "Wrong note type in SUB_FOLDER");
}
break;
default:
break;
}
}
}
}
private void startQueryDestinationFolders() {
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection:
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
}
}
return false;
}
public void OnOpenMenu(View view) {
openOptionsMenu();
}
}

@ -0,0 +1,184 @@
/*
* 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;
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 {
public int widgetId;
public int widgetType;
};
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
public boolean isInChoiceMode() {
return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
}
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
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);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
@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;
}
}
}
}

@ -0,0 +1,122 @@
/*
* 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;
}
}

@ -0,0 +1,388 @@
/*
* 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);
/* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true);
addPreferencesFromResource(R.xml.preferences);
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
@Override
protected void onResume() {
super.onResume();
// need to set sync account automatically if user has added a new
// account
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();
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
private void loadAccountPreference() {
mAccountCategory.removeAll();
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
showChangeAccountConfirmAlertDialog();
}
} else {
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
mAccountCategory.addPreference(accountPref);
}
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// set button state
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// set last sync time
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
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();
}
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();
// clean up last sync time
setLastSyncTime(this, 0);
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
editor.commit();
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
}
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit();
}
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
refreshUI();
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;
}
}
}

@ -0,0 +1,132 @@
/*
* 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;
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";
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
}
}
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
String snippet = "";
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
if (c != null) {
c.close();
}
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;
if (privacyMode) {
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
protected abstract int getBgResourceId(int bgId);
protected abstract int getLayoutId();
protected abstract int getWidgetType();
}

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

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

@ -0,0 +1,22 @@
<?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>

@ -0,0 +1,20 @@
<?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.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

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

Loading…
Cancel
Save