Compare commits

..

2 Commits

Author SHA1 Message Date
zhangqing 0fa9cdffef 1
3 months ago
zhangqing 75d64f946c 报告新版
3 months ago

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

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

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

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

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

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

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

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffcc">
<!-- Title Bar -->
<RelativeLayout
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="#383838"
android:gravity="center_vertical">
<ImageView
android:id="@+id/iv_back"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentLeft="true"
android:padding="12dp"
android:src="?android:attr/homeAsUpIndicator"
android:contentDescription="@string/menu_back" />
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#ffffff"
android:textSize="18sp"
android:text="@string/trash_title" />
</RelativeLayout>
<!-- Empty Trash Text -->
<TextView
android:id="@+id/tv_empty_trash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#999999"
android:textSize="16sp"
android:visibility="gone" />
<!-- Notes List -->
<ListView
android:id="@+id/notes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/title_bar"
android:divider="#e0e0e0"
android:dividerHeight="1dp"
android:listSelector="#00000000" />
</RelativeLayout>

@ -109,16 +109,6 @@
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/tv_word_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:layout_margin="8dip"
android:text="字数0"
android:textAppearance="@style/TextAppearanceSecondaryItem"
android:textSize="12sp" />
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"

@ -19,58 +19,60 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/note_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp">
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone" />
android:layout_gravity="center_vertical"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:textSize="16sp"/>
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:textAppearance="@style/TextAppearanceSecondaryItem"
android:textSize="12sp"/>
android:layout_gravity="center_vertical">
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:focusable="false"
android:clickable="false"
android:visibility="gone" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
</LinearLayout>
</LinearLayout>
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:visibility="gone" />
</LinearLayout>
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"
android:layout_marginTop="5dp"/>
android:layout_gravity="top|right"/>
</FrameLayout>

@ -26,7 +26,6 @@
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- Title Bar -->
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="fill_parent"
@ -38,61 +37,6 @@
android:textColor="#FFEAD1AE"
android:textSize="@dimen/text_font_size_medium" />
<!-- Search Bar -->
<LinearLayout
android:id="@+id/search_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_bg"
android:orientation="horizontal"
android:padding="5dp"
android:visibility="gone"
android:layout_marginTop="?android:attr/actionBarSize">
<ImageView
android:id="@+id/search_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
android:src="@android:drawable/ic_menu_search"
android:contentDescription="搜索" />
<EditText
android:id="@+id/search_edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:background="@null"
android:hint="搜索便签..."
android:singleLine="true"
android:textColor="#FFFFFF"
android:textColorHint="#FFB0B0B0"
android:textSize="@dimen/text_font_size_medium" />
<ImageView
android:id="@+id/clear_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
android:src="@android:drawable/ic_input_delete"
android:contentDescription="清除"
android:visibility="gone" />
<TextView
android:id="@+id/cancel_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"
android:text="取消"
android:textColor="#FFFFFF"
android:textSize="@dimen/text_font_size_medium" />
</LinearLayout>
<!-- Notes List View -->
<ListView
android:id="@+id/notes_list"
android:layout_width="fill_parent"
@ -101,36 +45,9 @@
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@null"
android:paddingTop="?android:attr/actionBarSize" />
<!-- Bottom Navigation Bar -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:orientation="horizontal"
android:padding="10dp">
<!-- Empty space to push menu button to the right -->
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<!-- Menu Button -->
<Button
android:id="@+id/menu_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:text="⋮"
android:textSize="24sp"
android:textColor="@android:color/black" />
</LinearLayout>
android:fadingEdge="@null" />
</LinearLayout>
<!-- New Note Button -->
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_suggestion_popup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:elevation="8dp"
android:orientation="vertical"
android:visibility="gone">
<ListView
android:id="@+id/search_suggestion_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="300dp"
android:scrollbars="vertical" />
</LinearLayout>

@ -49,16 +49,4 @@
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
<item
android:id="@+id/menu_sticky"
android:title="@string/menu_sticky" />
<item
android:id="@+id/menu_unsticky"
android:title="@string/menu_unsticky" />
<item
android:id="@+id/menu_rename"
android:title="重命名" />
</menu>

@ -36,8 +36,4 @@
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"/>
<item
android:id="@+id/menu_trash"
android:title="@string/menu_trash"/>
</menu>

@ -17,16 +17,6 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/sticky"
android:title="PIN"
android:icon="@drawable/clock"
android:showAsAction="always|withText" />
<item
android:id="@+id/unsticky"
android:title="UNPIN"
android:icon="@drawable/delete"
android:showAsAction="always|withText" />
<item
android:id="@+id/move"
android:title="@string/menu_move"

@ -21,7 +21,4 @@
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
<item
android:id="@+id/menu_rename_folder"
android:title="@string/menu_folder_change_name"/>
</menu>

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/restore"
android:title="@string/menu_restore"
android:icon="@drawable/new_note"
android:titleCondensed="恢复"
android:showAsAction="always|withText"
android:textAppearance="@android:style/TextAppearance.Large" />
<item
android:id="@+id/permanent_delete"
android:title="@string/menu_permanent_delete"
android:icon="@drawable/new_note"
android:titleCondensed="永久删除"
android:showAsAction="always|withText"
android:textAppearance="@android:style/TextAppearance.Large" />
</menu>

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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

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

@ -57,7 +57,7 @@
<string name="menu_normal_mode">退出清单模式</string>
<string name="menu_folder_view">查看文件夹</string>
<string name="menu_folder_delete">刪除文件夹</string>
<string name="menu_folder_change_name">重命名</string>
<string name="menu_folder_change_name">修改文件夹名称</string>
<string name="folder_exist">文件夹 %1$s 已存在,请重新命名</string>
<string name="menu_share">分享</string>
<string name="menu_send_to_desktop">发送到桌面</string>
@ -119,13 +119,6 @@
<string name="search">便签</string>
<string name="datetime_dialog_ok">设置</string>
<string name="datetime_dialog_cancel">取消</string>
<string name="menu_trash">回收站</string>
<string name="trash_title">回收站</string>
<string name="trash_empty">回收站是空的</string>
<string name="menu_restore">恢复</string>
<string name="menu_permanent_delete">永久删除</string>
<string name="alert_message_permanent_delete_notes">确认要永久删除所选的 %d 条便签吗?</string>
<string name="alert_message_permanent_delete_note">确认要永久删除该条便签吗?</string>
<plurals name="search_results_title">
<item quantity="other"><xliff:g id="NUMBER">%1$s</xliff:g> 条符合“<xliff:g id="SEARCH">%2$s</xliff:g>”的搜索结果</item>
</plurals>

@ -58,7 +58,7 @@
<string name="menu_normal_mode">退出清單模式</string>
<string name="menu_folder_view">查看文件夾</string>
<string name="menu_folder_delete">刪除文件夾</string>
<string name="menu_folder_change_name">重命名</string>
<string name="menu_folder_change_name">修改文件夾名稱</string>
<string name="folder_exist">文件夾 %1$s 已存在,請重新命名</string>
<string name="menu_share">分享</string>
<string name="menu_send_to_desktop">發送到桌面</string>
@ -120,13 +120,6 @@
<string name="search">便籤</string>
<string name="datetime_dialog_ok">設置</string>
<string name="datetime_dialog_cancel">取消</string>
<string name="menu_trash">回收站</string>
<string name="trash_title">回收站</string>
<string name="trash_empty">回收站是空的</string>
<string name="menu_restore">恢復</string>
<string name="menu_permanent_delete">永久刪除</string>
<string name="alert_message_permanent_delete_notes">確認要永久刪除所選的 %d 條便籤嗎?</string>
<string name="alert_message_permanent_delete_note">確認要永久刪除該條便籤嗎?</string>
<plurals name="search_results_title">
<item quantity="other"><xliff:g id="NUMBER">%1$s</xliff:g> 條符合”<xliff:g id="SEARCH">%2$s</xliff:g>“的搜尋結果</item>
</plurals>

@ -48,8 +48,6 @@
<string name="menu_search">Search</string>
<string name="menu_delete">Delete</string>
<string name="menu_move">Move to folder</string>
<string name="menu_sticky">置顶</string>
<string name="menu_unsticky">取消置顶</string>
<string name="menu_select_title">%d selected</string>
<string name="menu_select_none">Nothing selected, the operation is invalid</string>
<string name="menu_select_all">Select all</string>
@ -63,7 +61,7 @@
<string name="menu_normal_mode">Leave check list</string>
<string name="menu_folder_view">View folder</string>
<string name="menu_folder_delete">Delete folder</string>
<string name="menu_folder_change_name">重命名</string>
<string name="menu_folder_change_name">Change folder name</string>
<string name="folder_exist">The folder %1$s exist, please rename</string>
<string name="menu_share">Share</string>
<string name="menu_send_to_desktop">Send to home</string>
@ -128,18 +126,10 @@
<string name="search">Notes</string>
<string name="datetime_dialog_ok">set</string>
<string name="datetime_dialog_cancel">cancel</string>
<string name="menu_trash">Trash</string>
<string name="trash_title">Trash</string>
<string name="trash_empty">Trash is empty</string>
<string name="menu_restore">Restore</string>
<string name="menu_permanent_delete">Permanent Delete</string>
<string name="menu_back">Back</string>
<string name="alert_message_permanent_delete_notes">Confirm to permanently delete the selected %d notes?</string>
<string name="alert_message_permanent_delete_note">Confirm to permanently delete this note?</string>
<plurals name="search_results_title">
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> result for "<xliff:g id="search" example="???">%2$s</xliff:g>"</item>
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> result for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<!-- Case of 0 or 2 or more results. -->
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for "<xliff:g id="search" example="???">%2$s</xliff:g>"</item>
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
</plurals>
</resources>

@ -60,21 +60,10 @@
<style name="NoteTheme" parent="@android:style/Theme.Holo.Light">
<item name="android:actionBarStyle">@style/NoteActionBarStyle</item>
<item name="android:actionModeStyle">@style/NoteActionModeStyle</item>
<item name="android:actionMenuTextAppearance">@style/NoteActionMenuText</item>
</style>
<style name="NoteActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid">
<item name="android:visibility">visible</item>
</style>
<style name="NoteActionModeStyle" parent="@android:style/Widget.Holo.ActionMode">
<item name="android:background">#333333</item>
<item name="android:height">64dp</item>
</style>
<style name="NoteActionMenuText" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Menu">
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
<item name="android:displayOptions" />
<item name="android:visibility">gone</item>
</style>
</resources>

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

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

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

@ -25,86 +25,49 @@ import android.util.Log;
import java.util.HashMap;
/**
*
*
*
*/
public class Contact {
/**
*
*
*/
private static HashMap<String, String> sContactCache;
/**
*
*/
private static final String TAG = "Contact";
/**
* ID
*
*/
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
/**
*
*
* 1.
* 2.
* 3.
* 4.
* @param context
* @param phoneNumber
* @return null
*/
public static String getContact(Context context, String phoneNumber) {
// 如果缓存为空,则初始化缓存
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 先从缓存中查找,提高查询效率
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber); // 返回缓存中的姓名
return sContactCache.get(phoneNumber);
}
// 构建查询条件:替换选择语句中的'+'为最小匹配数
// 使用PhoneNumberUtils.toCallerIDMinMatch确保电话号码格式正确匹配
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 查询联系人数据库
// 使用ContentResolver查询系统联系人数据
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI, // 查询URI
new String [] { Phone.DISPLAY_NAME }, // 要返回的列:显示名称
selection, // 选择条件
new String[] { phoneNumber }, // 选择参数:电话号码
null); // 排序方式(无)
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
// 处理查询结果
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0); // 获取第一列的显示名称
sContactCache.put(phoneNumber, name); // 存入缓存,提高后续查询效率
return name; // 返回姓名
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; // 发生异常时返回null
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
cursor.close(); // 确保关闭游标,防止资源泄漏
cursor.close();
}
} else {
Log.d(TAG, "No contact matched with number:" + phoneNumber); // 记录调试日志
return null; // 没有匹配的联系人返回null
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}
}

@ -17,362 +17,263 @@
package net.micode.notes.data;
import android.net.Uri;
/**
*
* URI
*
*/
public class Notes {
// 内容提供者的授权标识用于ContentResolver访问
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; // 系统文件夹类型
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
/**
*
* {@link Notes#ID_ROOT_FOLDER }
* {@link Notes#ID_TEMPARAY_FOLDER }
* {@link Notes#ID_CALL_RECORD_FOLDER}
* {@link Notes#ID_TRASH_FOLDER}
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0; // 根文件夹ID所有笔记的默认父文件夹
public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID用于无明确父文件夹的笔记
public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹ID专门存储通话记录笔记
public static final int ID_TRASH_FOLDER = -3; // 回收站文件夹ID存储已删除的笔记
// Intent额外数据键名常量用于Activity间传递数据
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景颜色ID
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 小部件ID
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 小部件类型
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期
// 小部件类型常量,定义不同尺寸的桌面小部件
public static final int TYPE_WIDGET_INVALIDE = -1; // 无效小部件
public static final int TYPE_WIDGET_2X = 0; // 2x大小小部件
public static final int TYPE_WIDGET_4X = 1; // 4x大小小部件
public static 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;
/**
*
* MIMEContentProvider
*/
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记MIME类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话笔记MIME类型
public static final String PASSWORD = PasswordNote.CONTENT_ITEM_TYPE; // 密码MIME类型
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
/**
*
*/
public static final int PASSWORD_TYPE_NONE = 0; // 无密码
public static final int PASSWORD_TYPE_DIGIT = 1; // 数字密码
public static final int PASSWORD_TYPE_PATTERN = 2; // 图案密码
/**
* URI
* ContentResolver
* Uri to query all notes and folders
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* URI
* ContentResolver
* Uri to query data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
/**
*
*
*/
public interface NoteColumns {
/**
* ID
* <P> : INTEGER (long) </P>
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* ID
* <P> : INTEGER (long) </P>
* parent_id0
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String PARENT_ID = "parent_id";
/**
*
* <P> : INTEGER (long) </P>
*
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> : INTEGER (long) </P>
*
* Alert date
* <P> Type: INTEGER (long) </P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
*
* <P> : TEXT </P>
*
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
public static final String SNIPPET = "snippet";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
* Note's widget id
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_ID = "widget_id";
/**
*
* <P> : INTEGER (long) </P>
*
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
* Note's background color's id
* <P> Type: INTEGER (long) </P>
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
*
* <P> : INTEGER </P>
* 01
* 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";
/**
*
* <P> : INTEGER (long) </P>
*
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
*
* <P> : INTEGER </P>
* TYPE_NOTETYPE_FOLDERTYPE_SYSTEM
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
public static final String TYPE = "type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
public static final String SYNC_ID = "sync_id";
/**
*
* <P> : INTEGER </P>
* 01
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* ID
* <P> : INTEGER </P>
*
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* GoogleID
* <P> : TEXT </P>
* Google Tasks
* The gtask id
* <P> Type : TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
*
* <P> : INTEGER (long) </P>
*
* The version code
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
/**
*
* <P> : INTEGER </P>
* <P> : 1 - , 0 - </P>
*
*/
public static final String STICKY = "sticky";
}
/**
*
*
*/
public interface DataColumns {
/**
* ID
* <P> : INTEGER (long) </P>
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* MIME
* <P> : Text </P>
*
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
*
* <P> : INTEGER (long) </P>
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> : TEXT </P>
*
* Data's content
* <P> Type: TEXT </P>
*/
public static final String CONTENT = "content";
/**
* {@link #MIMETYPE}
* <P> : INTEGER </P>
* /
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA1 = "data1";
/**
* {@link #MIMETYPE}
* <P> : INTEGER </P>
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA2 = "data2";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
*
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA3 = "data3";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA4 = "data4";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA5 = "data5";
}
/**
*
* DataColumns
*/
public static final class TextNote implements DataColumns {
/**
*
* <P> : Integer 1: 0: </P>
* DATA1
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
public static final String MODE = DATA1;
public static final int MODE_CHECK_LIST = 1; // 清单模式
public static final int MODE_CHECK_LIST = 1;
// 内容类型常量用于ContentProvider返回数据的类型标识
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 String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 文本笔记的URI用于ContentResolver查询文本笔记数据
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
/**
*
* DataColumns
*/
public static final class CallNote implements DataColumns {
/**
*
* <P> : INTEGER (long) </P>
* DATA1
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1;
/**
*
* <P> : TEXT </P>
* DATA3
* Phone number for this record
* <P> Type: TEXT </P>
*/
public static final String PHONE_NUMBER = DATA3;
// 内容类型常量用于ContentProvider返回数据的类型标识
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // 多个通话笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; // 单个通话笔记
// 通话笔记的URI用于ContentResolver查询通话笔记数据
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
/**
*
* DataColumns
*/
public static final class PasswordNote implements DataColumns {
/**
*
* <P> : INTEGER </P>
* <P> : 0 - , 1 - , 2 - </P>
* DATA1
*/
public static final String PASSWORD_TYPE = DATA1;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
/**
*
* <P> : TEXT </P>
* DATA3
*/
public static final String PASSWORD_CONTENT = DATA3;
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 内容类型常量用于ContentProvider返回数据的类型标识
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/password_note"; // 多个密码笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/password_note"; // 单个密码笔记
// 密码笔记的URI用于ContentResolver查询密码数据
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/password_note");
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}
}

@ -26,247 +26,198 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
/**
*
* SQLiteOpenHelper
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称常量
private static final String DB_NAME = "note.db";
// 数据库版本常量,用于数据库升级
private static final int DB_VERSION = 5;
private static final int DB_VERSION = 4;
/**
*
*
*/
public interface TABLE {
// 笔记表名
public static final String NOTE = "note";
// 数据表名
public static final String DATA = "data";
}
// 日志标签,用于调试
private static final String TAG = "NotesDatabaseHelper";
// 单例实例
private static NotesDatabaseHelper mInstance;
/**
* SQL
*
*/
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" + // 创建笔记表
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 笔记数量(用于文件夹)
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 内容摘要
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 类型(笔记/文件夹/系统)
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标志
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父文件夹ID
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google任务ID
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," + // 版本号
NoteColumns.STICKY + " INTEGER NOT NULL DEFAULT 0" + // 置顶状态
")";
"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," + // 主键ID
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 内容
DataColumns.DATA1 + " INTEGER," + // 通用数据字段1整数
DataColumns.DATA2 + " INTEGER," + // 通用数据字段2整数
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 通用数据字段3文本
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 通用数据字段4文本
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 通用数据字段5文本
")";
"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 ''" +
")";
/**
* SQL
* note_id
*/
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " + // 创建索引
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; // 在note_id字段上创建索引
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
*
* ID
* Increase folder's note count when move note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+ // 创建触发器
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + // 在PARENT_ID更新后触发
" BEGIN " + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 笔记计数加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 更新新父文件夹
" END"; // 触发器结束
"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";
/**
*
* ID
* Decrease folder's note count when move note from folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " + // 创建触发器
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + // 在PARENT_ID更新后触发
" BEGIN " + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + // 笔记计数减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 更新原父文件夹
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + // 确保计数不小于0
" END"; // 触发器结束
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
/**
*
*
* Increase folder's note count when insert new note to the folder
*/
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" + // 笔记计数加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 更新父文件夹
" END"; // 触发器结束
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
*
*
* Decrease folder's note count when delete note from the folder
*/
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" + // 笔记计数减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 更新原父文件夹
" AND " + NoteColumns.NOTES_COUNT + ">0;" + // 确保计数不小于0
" END"; // 触发器结束
"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";
/**
*
* NOTE
* Update note's content when insert data with type {@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 + "'" + // 仅当MIME类型为NOTE时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 设置摘要为内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 更新对应的笔记
" END"; // 触发器结束
"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";
/**
*
* NOTE
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " + // 创建触发器
" AFTER UPDATE ON " + TABLE.DATA + // 在更新数据后触发
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 仅当MIME类型为NOTE时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 设置摘要为内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 更新对应的笔记
" END"; // 触发器结束
"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";
/**
*
* NOTE
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " + // 创建触发器
" AFTER delete ON " + TABLE.DATA + // 在删除数据后触发
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 仅当MIME类型为NOTE时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.SNIPPET + "=''" + // 清空摘要
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + // 更新对应的笔记
" END"; // 触发器结束
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
/**
*
*
* Delete datas belong to note which has been deleted
*/
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 + ";" + // 删除笔记ID对应的数据
" END"; // 触发器结束
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
*
*
* Delete notes belong to folder which has been deleted
*/
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 + ";" + // 删除父文件夹ID为被删除ID的笔记
" END"; // 触发器结束
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
*
*
* Move notes belong to folder which has been moved to trash folder
*/
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_FOLDER + // 仅当新父文件夹是回收站时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLDER + // 设置父文件夹为回收站
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 更新原文件夹下的所有笔记
" END"; // 触发器结束
"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";
/**
*
* @param context
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION); // 调用父类构造函数
super(context, DB_NAME, null, DB_VERSION);
}
/**
*
* @param db SQLite
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建笔记表的SQL语句
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created"); // 日志记录
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
/**
*
* @param db SQLite
*/
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");
@ -275,7 +226,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 重新创建触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
@ -285,202 +235,128 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
/**
*
* @param db SQLite
*/
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues(); // 创建内容值对象
ContentValues values = new ContentValues();
/**
*
*
* call record foler for call notes
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); // 设置ID为通话记录文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
*
*
* root folder which is default folder
*/
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); // 设置ID为根文件夹ID
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);
/**
*
*
* temporary folder which is used for moving note
*/
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); // 设置ID为临时文件夹ID
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);
/**
*
*
* create trash folder
*/
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLDER); // 设置ID为回收站文件夹ID
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);
}
/**
*
* @param db SQLite
*/
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建数据表的SQL语句
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建数据表索引
Log.d(TAG, "data table has been created"); // 日志记录
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created");
}
/**
*
* @param db SQLite
*/
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);
}
/**
*
* 使
* @param context
* @return
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) { // 如果实例为空
mInstance = new NotesDatabaseHelper(context); // 创建新实例
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance; // 返回实例
return mInstance;
}
/**
*
*
* @param db SQLite
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据表
createNoteTable(db);
createDataTable(db);
}
/**
*
*
* @param db SQLite
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 是否需要重新创建触发器
boolean skipV2 = false; // 是否跳过V2升级
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 从版本1升级到版本2
if (oldVersion == 1) {
upgradeToV2(db); // 执行V2升级
skipV2 = true; // 这个升级包含了从V2到V3的升级
oldVersion++; // 增加版本号
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
}
// 从版本2升级到版本3如果没有跳过
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); // 执行V3升级
reCreateTriggers = true; // 需要重新创建触发器
oldVersion++; // 增加版本号
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
// 从版本3升级到版本4
if (oldVersion == 3) {
upgradeToV4(db); // 执行V4升级
oldVersion++; // 增加版本号
}
// 从版本4升级到版本5
if (oldVersion == 4) {
upgradeToV5(db); // 执行V5升级
oldVersion++; // 增加版本号
upgradeToV4(db);
oldVersion++;
}
// 如果需要重新创建触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db); // 重新创建笔记表触发器
reCreateDataTableTriggers(db); // 重新创建数据表触发器
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 如果升级后版本号不匹配,抛出异常
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
/**
* V2
*
* @param db SQLite
*/
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); // 删除已存在的note表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); // 删除已存在的data表
createNoteTable(db); // 重新创建note表
createDataTable(db); // 重新创建data表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db);
createDataTable(db);
}
/**
* V3
* GoogleID
* @param db SQLite
*/
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");
// 添加Google任务ID字段
// add a column for gtask id
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// 添加回收站系统文件夹
ContentValues values = new ContentValues(); // 创建内容值对象
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLDER); // 设置ID为回收站文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
// 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);
}
/**
* V4
*
* @param db SQLite
*/
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); // 添加版本号字段
}
/**
* V5
*
* @param db SQLite
*/
private void upgradeToV5(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.STICKY
+ " INTEGER NOT NULL DEFAULT 0"); // 添加置顶状态字段
+ " INTEGER NOT NULL DEFAULT 0");
}
}
}

@ -34,359 +34,272 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
/**
*
* CRUD
* 访
*/
public class NotesProvider extends ContentProvider {
// URI匹配器用于匹配不同的URI请求将不同的URI映射到对应的操作
private static final UriMatcher mMatcher;
// 数据库帮助类实例,负责数据库的创建和版本管理
private NotesDatabaseHelper mHelper;
// 日志标签
private static final String TAG = "NotesProvider";
// URI匹配码常量用于标识不同类型的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; // 搜索建议
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;
// 静态初始化URI匹配器在类加载时执行
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 创建URI匹配器初始值为NO_MATCH
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配笔记表(所有笔记)
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配单个笔记通过ID
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配数据表(所有数据)
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配单个数据通过ID
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); // 匹配带查询词的搜索建议
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'
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," // 笔记ID
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 作为建议的额外数据
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 建议文本1标题
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 建议文本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; // 建议意图数据类型
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;
/**
*
*
*
*/
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION // 选择搜索投影
+ " FROM " + TABLE.NOTE // 从笔记表
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 条件摘要LIKE参数模糊搜索
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER // 且不在回收站中
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 且类型为笔记
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;
/**
*
*
* @return true
*/
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助类实例(单例模式)
return true; // 创建成功
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
/**
*
* URI
* @param uri URI
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null; // 游标,用于返回查询结果
try {
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库
String id = null; // ID变量用于单个项目的查询
// 设置默认排序顺序:按置顶状态降序,修改时间降序
if (sortOrder == null && (mMatcher.match(uri) == URI_NOTE || mMatcher.match(uri) == URI_NOTE_ITEM)) {
sortOrder = NoteColumns.STICKY + " DESC, " + NoteColumns.MODIFIED_DATE + " DESC";
}
switch (mMatcher.match(uri)) { // 根据URI匹配码进行分支
case URI_NOTE: // 查询笔记表(所有笔记)
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM: // 查询单个笔记
id = uri.getPathSegments().get(1); // 从URI路径段获取笔记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;
case URI_DATA_ITEM: // 查询单个数据
id = uri.getPathSegments().get(1); // 从URI路径段获取数据ID
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH: // 搜索
case URI_SEARCH_SUGGEST: // 搜索建议
// 搜索和搜索建议不支持自定义排序和投影
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
String sortOrder) {
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
switch (mMatcher.match(uri)) {
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
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;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
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) {
// 从URI路径段获取搜索建议查询词
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
// 从查询参数获取搜索模式
searchString = uri.getQueryParameter("pattern");
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; // 返回空
}
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
searchString = String.format("%%%s%%", searchString); // 格式化为LIKE模式前后加%
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString }); // 执行原始查询
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString()); // 记录异常
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri); // 设置通知URI当数据变化时通知观察者
}
} catch (Exception e) {
Log.e(TAG, "Error querying database: " + e.toString());
try {
searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c; // 返回游标
return c;
}
/**
*
* URI
* @param uri URI
* @param values
* @return URIID
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
try {
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
long dataId = 0, noteId = 0, insertedId = 0; // 插入的ID
switch (mMatcher.match(uri)) {
case URI_NOTE: // 插入笔记
insertedId = noteId = db.insert(TABLE.NOTE, null, values); // 插入笔记表
break;
case URI_DATA: // 插入数据
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID); // 获取笔记ID数据所属的笔记
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString()); // 记录错误
}
insertedId = dataId = db.insert(TABLE.DATA, null, values); // 插入数据表
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
// 通知笔记URI变化如果插入了笔记
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 通知数据URI变化如果插入了数据
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
switch (mMatcher.match(uri)) {
case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
return ContentUris.withAppendedId(uri, insertedId); // 返回插入的URI包含新记录的ID
} catch (Exception e) {
Log.e(TAG, "Error inserting data: " + e.toString());
return null;
// Notify the data uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId);
}
/**
*
* URI
* @param uri URI
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0; // 删除计数
try {
String id = null; // ID变量用于单个项目的删除
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
boolean deleteData = false; // 是否删除数据标志
switch (mMatcher.match(uri)) {
case URI_NOTE: // 删除笔记
// 系统文件夹不允许删除ID小于等于0
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs); // 删除笔记表中的记录
break;
case URI_NOTE_ITEM: // 删除单个笔记
id = uri.getPathSegments().get(1); // 从URI路径段获取笔记ID
long noteId = Long.valueOf(id); // 转换为长整型
if (noteId <= 0) { // 系统文件夹不允许删除
break;
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的笔记
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
break;
case URI_DATA: // 删除数据
count = db.delete(TABLE.DATA, selection, selectionArgs); // 删除数据表中的记录
deleteData = true; // 标记为删除数据
break;
case URI_DATA_ITEM: // 删除单个数据
id = uri.getPathSegments().get(1); // 从URI路径段获取数据ID
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的数据
deleteData = true; // 标记为删除数据
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
if (count > 0) { // 如果删除了数据
if (deleteData) { // 如果是删除数据
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变化
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
case URI_DATA_ITEM:
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);
}
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
} catch (Exception e) {
Log.e(TAG, "Error deleting data: " + e.toString());
getContext().getContentResolver().notifyChange(uri, null);
}
return count; // 返回删除计数
return count;
}
/**
*
* URI
* @param uri URI
* @param values
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0; // 更新计数
try {
String id = null; // ID变量用于单个项目的更新
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
boolean updateData = false; // 是否更新数据标志
switch (mMatcher.match(uri)) {
case URI_NOTE: // 更新笔记
increaseNoteVersion(-1, selection, selectionArgs); // 增加笔记版本(批量更新)
count = db.update(TABLE.NOTE, values, selection, selectionArgs); // 更新笔记表中的记录
break;
case URI_NOTE_ITEM: // 更新单个笔记
id = uri.getPathSegments().get(1); // 从URI路径段获取笔记ID
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 增加笔记版本(单个更新)
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 更新指定ID的笔记
break;
case URI_DATA: // 更新数据
count = db.update(TABLE.DATA, values, selection, selectionArgs); // 更新数据表中的记录
updateData = true; // 标记为更新数据
break;
case URI_DATA_ITEM: // 更新单个数据
id = uri.getPathSegments().get(1); // 从URI路径段获取数据ID
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 更新指定ID的数据
updateData = true; // 标记为更新数据
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
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:
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) { // 如果更新了数据
if (updateData) { // 如果是更新数据
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变化
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
} catch (Exception e) {
Log.e(TAG, "Error updating data: " + e.toString());
getContext().getContentResolver().notifyChange(uri, null);
}
return count; // 返回更新计数
return count;
}
/**
*
* AND
* @param selection
* @return
*/
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); // 如果不为空则添加AND和括号
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
/**
*
*
* @param id ID-1使
* @param selection
* @param selectionArgs
*/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
try {
SQLiteDatabase db = mHelper.getWritableDatabase();
// 使用SQL语句直接更新版本号确保版本号正确递增
StringBuilder sql = new StringBuilder();
sql.append("UPDATE " + TABLE.NOTE + " SET " + NoteColumns.VERSION + " = " + NoteColumns.VERSION + " + 1");
if (id > 0) {
sql.append(" WHERE " + NoteColumns.ID + " = " + id); // 更新指定ID的笔记版本
} else if (!TextUtils.isEmpty(selection)) {
sql.append(" WHERE " + selection); // 使用选择语句更新多个笔记版本
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
db.execSQL(sql.toString()); // 执行SQL语句
} catch (Exception e) {
Log.e(TAG, "Error increasing note version: " + e.toString());
sql.append(selectString);
}
mHelper.getWritableDatabase().execSQL(sql.toString());
}
/**
* MIME
* null
* @param uri URI
* @return MIME
*/
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null; // 暂未实现
return null;
}
}
}

@ -1,110 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
/**
*
*
*
*/
public class Contact {
/**
*
*
*/
private static HashMap<String, String> sContactCache;
/**
*
*/
private static final String TAG = "Contact";
/**
* ID
*
*/
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
/**
*
*
* 1.
* 2.
* 3.
* 4.
* @param context
* @param phoneNumber
* @return null
*/
public static String getContact(Context context, String phoneNumber) {
// 如果缓存为空,则初始化缓存
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 先从缓存中查找,提高查询效率
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber); // 返回缓存中的姓名
}
// 构建查询条件:替换选择语句中的'+'为最小匹配数
// 使用PhoneNumberUtils.toCallerIDMinMatch确保电话号码格式正确匹配
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 查询联系人数据库
// 使用ContentResolver查询系统联系人数据
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI, // 查询URI
new String [] { Phone.DISPLAY_NAME }, // 要返回的列:显示名称
selection, // 选择条件
new String[] { phoneNumber }, // 选择参数:电话号码
null); // 排序方式(无)
// 处理查询结果
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0); // 获取第一列的显示名称
sContactCache.put(phoneNumber, name); // 存入缓存,提高后续查询效率
return name; // 返回姓名
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, " Cursor get string error " + e.toString()); // 记录错误日志
return null; // 发生异常时返回null
} finally {
cursor.close(); // 确保关闭游标,防止资源泄漏
}
} else {
Log.d(TAG, "No contact matched with number:" + phoneNumber); // 记录调试日志
return null; // 没有匹配的联系人返回null
}
}
}

@ -1,378 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.net.Uri;
/**
*
* URI
*
*/
public class Notes {
// 内容提供者的授权标识用于ContentResolver访问
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; // 系统文件夹类型
/**
*
* {@link Notes#ID_ROOT_FOLDER }
* {@link Notes#ID_TEMPARAY_FOLDER }
* {@link Notes#ID_CALL_RECORD_FOLDER}
* {@link Notes#ID_TRASH_FOLDER}
*/
public static final int ID_ROOT_FOLDER = 0; // 根文件夹ID所有笔记的默认父文件夹
public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID用于无明确父文件夹的笔记
public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹ID专门存储通话记录笔记
public static final int ID_TRASH_FOLDER = -3; // 回收站文件夹ID存储已删除的笔记
// Intent额外数据键名常量用于Activity间传递数据
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景颜色ID
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 小部件ID
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 小部件类型
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期
// 小部件类型常量,定义不同尺寸的桌面小部件
public static final int TYPE_WIDGET_INVALIDE = -1; // 无效小部件
public static final int TYPE_WIDGET_2X = 0; // 2x大小小部件
public static final int TYPE_WIDGET_4X = 1; // 4x大小小部件
/**
*
* MIMEContentProvider
*/
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记MIME类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话笔记MIME类型
public static final String PASSWORD = PasswordNote.CONTENT_ITEM_TYPE; // 密码MIME类型
}
/**
*
*/
public static final int PASSWORD_TYPE_NONE = 0; // 无密码
public static final int PASSWORD_TYPE_DIGIT = 1; // 数字密码
public static final int PASSWORD_TYPE_PATTERN = 2; // 图案密码
/**
* URI
* ContentResolver
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* URI
* ContentResolver
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
/**
*
*
*/
public interface NoteColumns {
/**
* ID
* <P> : INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* ID
* <P> : INTEGER (long) </P>
* parent_id0
*/
public static final String PARENT_ID = "parent_id";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String ALERTED_DATE = "alert_date";
/**
*
* <P> : TEXT </P>
*
*/
public static final String SNIPPET = "snippet";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String WIDGET_ID = "widget_id";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
*
* <P> : INTEGER </P>
* 01
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String NOTES_COUNT = "notes_count";
/**
*
* <P> : INTEGER </P>
* TYPE_NOTETYPE_FOLDERTYPE_SYSTEM
*/
public static final String TYPE = "type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String SYNC_ID = "sync_id";
/**
*
* <P> : INTEGER </P>
* 01
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* ID
* <P> : INTEGER </P>
*
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* GoogleID
* <P> : TEXT </P>
* Google Tasks
*/
public static final String GTASK_ID = "gtask_id";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String VERSION = "version";
/**
*
* <P> : INTEGER </P>
* <P> : 1 - , 0 - </P>
*
*/
public static final String STICKY = "sticky";
}
/**
*
*
*/
public interface DataColumns {
/**
* ID
* <P> : INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* MIME
* <P> : Text </P>
*
*/
public static final String MIME_TYPE = "mime_type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String NOTE_ID = "note_id";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> : TEXT </P>
*
*/
public static final String CONTENT = "content";
/**
* {@link #MIMETYPE}
* <P> : INTEGER </P>
* /
*/
public static final String DATA1 = "data1";
/**
* {@link #MIMETYPE}
* <P> : INTEGER </P>
*/
public static final String DATA2 = "data2";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
*
*/
public static final String DATA3 = "data3";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
*/
public static final String DATA4 = "data4";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
*/
public static final String DATA5 = "data5";
}
/**
*
* DataColumns
*/
public static final class TextNote implements DataColumns {
/**
*
* <P> : Integer 1: 0: </P>
* DATA1
*/
public static final String MODE = DATA1;
public static final int MODE_CHECK_LIST = 1; // 清单模式
// 内容类型常量用于ContentProvider返回数据的类型标识
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // 多个文本笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; // 单个文本笔记
// 文本笔记的URI用于ContentResolver查询文本笔记数据
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
/**
*
* DataColumns
*/
public static final class CallNote implements DataColumns {
/**
*
* <P> : INTEGER (long) </P>
* DATA1
*/
public static final String CALL_DATE = DATA1;
/**
*
* <P> : TEXT </P>
* DATA3
*/
public static final String PHONE_NUMBER = DATA3;
// 内容类型常量用于ContentProvider返回数据的类型标识
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // 多个通话笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; // 单个通话笔记
// 通话笔记的URI用于ContentResolver查询通话笔记数据
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
/**
*
* DataColumns
*/
public static final class PasswordNote implements DataColumns {
/**
*
* <P> : INTEGER </P>
* <P> : 0 - , 1 - , 2 - </P>
* DATA1
*/
public static final String PASSWORD_TYPE = DATA1;
/**
*
* <P> : TEXT </P>
* DATA3
*/
public static final String PASSWORD_CONTENT = DATA3;
// 内容类型常量用于ContentProvider返回数据的类型标识
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/password_note"; // 多个密码笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/password_note"; // 单个密码笔记
// 密码笔记的URI用于ContentResolver查询密码数据
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/password_note");
}
}

@ -1,486 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
/**
*
* SQLiteOpenHelper
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称常量
private static final String DB_NAME = "note.db";
// 数据库版本常量,用于数据库升级
private static final int DB_VERSION = 5;
/**
*
*
*/
public interface TABLE {
// 笔记表名
public static final String NOTE = "note";
// 数据表名
public static final String DATA = "data";
}
// 日志标签,用于调试
private static final String TAG = "NotesDatabaseHelper";
// 单例实例
private static NotesDatabaseHelper mInstance;
/**
* SQL
*
*/
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" + // 创建笔记表
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 笔记数量(用于文件夹)
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 内容摘要
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 类型(笔记/文件夹/系统)
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标志
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父文件夹ID
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google任务ID
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," + // 版本号
NoteColumns.STICKY + " INTEGER NOT NULL DEFAULT 0" + // 置顶状态
")";
/**
* SQL
*
*/
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" + // 创建数据表
DataColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 内容
DataColumns.DATA1 + " INTEGER," + // 通用数据字段1整数
DataColumns.DATA2 + " INTEGER," + // 通用数据字段2整数
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 通用数据字段3文本
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 通用数据字段4文本
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 通用数据字段5文本
")";
/**
* SQL
* note_id
*/
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " + // 创建索引
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; // 在note_id字段上创建索引
/**
*
* 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 + // 在PARENT_ID更新后触发
" BEGIN " + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 笔记计数加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 更新新父文件夹
" END"; // 触发器结束
/**
*
* ID
*/
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 + // 在PARENT_ID更新后触发
" BEGIN " + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + // 笔记计数减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 更新原父文件夹
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + // 确保计数不小于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" + // 笔记计数加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" + // 笔记计数减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 更新原父文件夹
" AND " + NoteColumns.NOTES_COUNT + ">0;" + // 确保计数不小于0
" END"; // 触发器结束
/**
*
* 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 + "'" + // 仅当MIME类型为NOTE时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 设置摘要为内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 更新对应的笔记
" END"; // 触发器结束
/**
*
* 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 + "'" + // 仅当MIME类型为NOTE时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 设置摘要为内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 更新对应的笔记
" END"; // 触发器结束
/**
*
* 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 + "'" + // 仅当MIME类型为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 + ";" + // 删除笔记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 + ";" + // 删除父文件夹ID为被删除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_FOLDER + // 仅当新父文件夹是回收站时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLDER + // 设置父文件夹为回收站
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 更新原文件夹下的所有笔记
" END"; // 触发器结束
/**
*
* @param context
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION); // 调用父类构造函数
}
/**
*
* @param db SQLite
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建笔记表的SQL语句
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created"); // 日志记录
}
/**
*
* @param db SQLite
*/
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 SQLite
*/
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues(); // 创建内容值对象
/**
*
*
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); // 设置ID为通话记录文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
/**
*
*
*/
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); // 设置ID为根文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
/**
*
*
*/
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); // 设置ID为临时文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
/**
*
*
*/
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLDER); // 设置ID为回收站文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
}
/**
*
* @param db SQLite
*/
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建数据表的SQL语句
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建数据表索引
Log.d(TAG, "data table has been created"); // 日志记录
}
/**
*
* @param db SQLite
*/
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);
}
/**
*
* 使
* @param context
* @return
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) { // 如果实例为空
mInstance = new NotesDatabaseHelper(context); // 创建新实例
}
return mInstance; // 返回实例
}
/**
*
*
* @param db SQLite
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据表
}
/**
*
*
* @param db SQLite
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 是否需要重新创建触发器
boolean skipV2 = false; // 是否跳过V2升级
// 从版本1升级到版本2
if (oldVersion == 1) {
upgradeToV2(db); // 执行V2升级
skipV2 = true; // 这个升级包含了从V2到V3的升级
oldVersion++; // 增加版本号
}
// 从版本2升级到版本3如果没有跳过
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); // 执行V3升级
reCreateTriggers = true; // 需要重新创建触发器
oldVersion++; // 增加版本号
}
// 从版本3升级到版本4
if (oldVersion == 3) {
upgradeToV4(db); // 执行V4升级
oldVersion++; // 增加版本号
}
// 从版本4升级到版本5
if (oldVersion == 4) {
upgradeToV5(db); // 执行V5升级
oldVersion++; // 增加版本号
}
// 如果需要重新创建触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db); // 重新创建笔记表触发器
reCreateDataTableTriggers(db); // 重新创建数据表触发器
}
// 如果升级后版本号不匹配,抛出异常
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
/**
* V2
*
* @param db SQLite
*/
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); // 删除已存在的note表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); // 删除已存在的data表
createNoteTable(db); // 重新创建note表
createDataTable(db); // 重新创建data表
}
/**
* V3
* GoogleID
* @param db SQLite
*/
private void upgradeToV3(SQLiteDatabase db) {
// 删除未使用的触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// 添加Google任务ID字段
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// 添加回收站系统文件夹
ContentValues values = new ContentValues(); // 创建内容值对象
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLDER); // 设置ID为回收站文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
}
/**
* V4
*
* @param db SQLite
*/
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); // 添加版本号字段
}
/**
* V5
*
* @param db SQLite
*/
private void upgradeToV5(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.STICKY
+ " INTEGER NOT NULL DEFAULT 0"); // 添加置顶状态字段
}
}

@ -1,392 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
/**
*
* CRUD
* 访
*/
public class NotesProvider extends ContentProvider {
// URI匹配器用于匹配不同的URI请求将不同的URI映射到对应的操作
private static final UriMatcher mMatcher;
// 数据库帮助类实例,负责数据库的创建和版本管理
private NotesDatabaseHelper mHelper;
// 日志标签
private static final String TAG = "NotesProvider";
// URI匹配码常量用于标识不同类型的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; // 搜索建议
// 静态初始化URI匹配器在类加载时执行
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 创建URI匹配器初始值为NO_MATCH
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配笔记表(所有笔记)
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配单个笔记通过ID
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配数据表(所有数据)
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配单个数据通过ID
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 + "," // 笔记ID
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 作为建议的额外数据
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 建议文本1标题
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 建议文本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; // 建议意图数据类型
/**
*
*
*
*/
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION // 选择搜索投影
+ " FROM " + TABLE.NOTE // 从笔记表
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 条件摘要LIKE参数模糊搜索
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER // 且不在回收站中
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 且类型为笔记
/**
*
*
* @return true
*/
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助类实例(单例模式)
return true; // 创建成功
}
/**
*
* URI
* @param uri URI
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null; // 游标,用于返回查询结果
try {
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库
String id = null; // ID变量用于单个项目的查询
// 设置默认排序顺序:按置顶状态降序,修改时间降序
if (sortOrder == null && (mMatcher.match(uri) == URI_NOTE || mMatcher.match(uri) == URI_NOTE_ITEM)) {
sortOrder = NoteColumns.STICKY + " DESC, " + NoteColumns.MODIFIED_DATE + " DESC";
}
switch (mMatcher.match(uri)) { // 根据URI匹配码进行分支
case URI_NOTE: // 查询笔记表(所有笔记)
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM: // 查询单个笔记
id = uri.getPathSegments().get(1); // 从URI路径段获取笔记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;
case URI_DATA_ITEM: // 查询单个数据
id = uri.getPathSegments().get(1); // 从URI路径段获取数据ID
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH: // 搜索
case URI_SEARCH_SUGGEST: // 搜索建议
// 搜索和搜索建议不支持自定义排序和投影
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
String searchString = null; // 搜索字符串
// 获取搜索字符串
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
// 从URI路径段获取搜索建议查询词
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); // 格式化为LIKE模式前后加%
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString }); // 执行原始查询
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString()); // 记录异常
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri); // 设置通知URI当数据变化时通知观察者
}
} catch (Exception e) {
Log.e(TAG, "Error querying database: " + e.toString());
}
return c; // 返回游标
}
/**
*
* URI
* @param uri URI
* @param values
* @return URIID
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
try {
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
long dataId = 0, noteId = 0, insertedId = 0; // 插入的ID
switch (mMatcher.match(uri)) {
case URI_NOTE: // 插入笔记
insertedId = noteId = db.insert(TABLE.NOTE, null, values); // 插入笔记表
break;
case URI_DATA: // 插入数据
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID); // 获取笔记ID数据所属的笔记
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString()); // 记录错误
}
insertedId = dataId = db.insert(TABLE.DATA, null, values); // 插入数据表
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
// 通知笔记URI变化如果插入了笔记
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 通知数据URI变化如果插入了数据
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId); // 返回插入的URI包含新记录的ID
} catch (Exception e) {
Log.e(TAG, "Error inserting data: " + e.toString());
return null;
}
}
/**
*
* URI
* @param uri URI
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0; // 删除计数
try {
String id = null; // ID变量用于单个项目的删除
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
boolean deleteData = false; // 是否删除数据标志
switch (mMatcher.match(uri)) {
case URI_NOTE: // 删除笔记
// 系统文件夹不允许删除ID小于等于0
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs); // 删除笔记表中的记录
break;
case URI_NOTE_ITEM: // 删除单个笔记
id = uri.getPathSegments().get(1); // 从URI路径段获取笔记ID
long noteId = Long.valueOf(id); // 转换为长整型
if (noteId <= 0) { // 系统文件夹不允许删除
break;
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的笔记
break;
case URI_DATA: // 删除数据
count = db.delete(TABLE.DATA, selection, selectionArgs); // 删除数据表中的记录
deleteData = true; // 标记为删除数据
break;
case URI_DATA_ITEM: // 删除单个数据
id = uri.getPathSegments().get(1); // 从URI路径段获取数据ID
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的数据
deleteData = true; // 标记为删除数据
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
if (count > 0) { // 如果删除了数据
if (deleteData) { // 如果是删除数据
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变化
}
} catch (Exception e) {
Log.e(TAG, "Error deleting data: " + e.toString());
}
return count; // 返回删除计数
}
/**
*
* URI
* @param uri URI
* @param values
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0; // 更新计数
try {
String id = null; // ID变量用于单个项目的更新
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
boolean updateData = false; // 是否更新数据标志
switch (mMatcher.match(uri)) {
case URI_NOTE: // 更新笔记
increaseNoteVersion(-1, selection, selectionArgs); // 增加笔记版本(批量更新)
count = db.update(TABLE.NOTE, values, selection, selectionArgs); // 更新笔记表中的记录
break;
case URI_NOTE_ITEM: // 更新单个笔记
id = uri.getPathSegments().get(1); // 从URI路径段获取笔记ID
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 增加笔记版本(单个更新)
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 更新指定ID的笔记
break;
case URI_DATA: // 更新数据
count = db.update(TABLE.DATA, values, selection, selectionArgs); // 更新数据表中的记录
updateData = true; // 标记为更新数据
break;
case URI_DATA_ITEM: // 更新单个数据
id = uri.getPathSegments().get(1); // 从URI路径段获取数据ID
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 更新指定ID的数据
updateData = true; // 标记为更新数据
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri); // 未知URI异常
}
if (count > 0) { // 如果更新了数据
if (updateData) { // 如果是更新数据
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变化
}
} catch (Exception e) {
Log.e(TAG, "Error updating data: " + e.toString());
}
return count; // 返回更新计数
}
/**
*
* AND
* @param selection
* @return
*/
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); // 如果不为空则添加AND和括号
}
/**
*
*
* @param id ID-1使
* @param selection
* @param selectionArgs
*/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
try {
SQLiteDatabase db = mHelper.getWritableDatabase();
// 使用SQL语句直接更新版本号确保版本号正确递增
StringBuilder sql = new StringBuilder();
sql.append("UPDATE " + TABLE.NOTE + " SET " + NoteColumns.VERSION + " = " + NoteColumns.VERSION + " + 1");
if (id > 0) {
sql.append(" WHERE " + NoteColumns.ID + " = " + id); // 更新指定ID的笔记版本
} else if (!TextUtils.isEmpty(selection)) {
sql.append(" WHERE " + selection); // 使用选择语句更新多个笔记版本
}
db.execSQL(sql.toString()); // 执行SQL语句
} catch (Exception e) {
Log.e(TAG, "Error increasing note version: " + e.toString());
}
}
/**
* MIME
* null
* @param uri URI
* @return MIME
*/
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null; // 暂未实现
}
}

@ -1,128 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* MetaData
* Google Task
* Task
*/
public class MetaData extends Task {
// 日志标签
private final static String TAG = MetaData.class.getSimpleName();
// 关联的Google Task ID
private String mRelatedGid = null;
/**
*
* Google Task ID
* @param gid Google Task ID
* @param metaInfo JSON
*/
public void setMeta(String gid, JSONObject metaInfo) {
try {
// 将Google Task ID添加到元信息中
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
// 将元信息JSON字符串设置为笔记内容
setNotes(metaInfo.toString());
// 设置任务名称为元数据笔记的特定名称
setName(GTaskStringUtils.META_NOTE_NAME);
}
/**
* Google Task ID
* @return Google Task ID
*/
public String getRelatedGid() {
return mRelatedGid;
}
/**
*
*
* @return
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
/**
* JSON
* JSONGoogle Task ID
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js); // 调用父类方法设置基础内容
if (getNotes() != null) {
try {
// 从笔记内容中解析元信息JSON
JSONObject metaInfo = new JSONObject(getNotes().trim());
// 提取关联的Google Task ID
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null; // 解析失败时设置为null
}
}
}
/**
* JSON
* MetaDataJSON
* @param js JSON
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
// 此函数不应被调用因为MetaData不应从本地JSON加载
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
/**
* JSON
* MetaDataJSON
* @return JSON
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
/**
*
* MetaData
* @param c
* @return
*/
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -1,168 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
/**
* Node
*
*
*/
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; // 同步错误
// Google Task ID
private String mGid;
// 节点名称
private String mName;
// 最后修改时间戳
private long mLastModified;
// 是否已删除
private boolean mDeleted;
/**
*
*
*/
public Node() {
mGid = null; // Google Task ID初始为null
mName = ""; // 名称初始为空字符串
mLastModified = 0; // 最后修改时间初始为0
mDeleted = false; // 删除标志初始为false
}
/**
* JSON
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getCreateAction(int actionId);
/**
* JSON
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getUpdateAction(int actionId);
/**
* JSON
*
* @param js JSON
*/
public abstract void setContentByRemoteJSON(JSONObject js);
/**
* JSON
*
* @param js JSON
*/
public abstract void setContentByLocalJSON(JSONObject js);
/**
* JSON
*
* @return JSON
*/
public abstract JSONObject getLocalJSONFromContent();
/**
*
*
* @param c
* @return
*/
public abstract int getSyncAction(Cursor c);
/**
* Google Task ID
* @param gid Google Task ID
*/
public void setGid(String gid) {
this.mGid = gid;
}
/**
*
* @param name
*/
public void setName(String name) {
this.mName = name;
}
/**
*
* @param lastModified
*/
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
/**
*
* @param deleted
*/
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
/**
* Google Task ID
* @return Google Task ID
*/
public String getGid() {
return this.mGid;
}
/**
*
* @return
*/
public String getName() {
return this.mName;
}
/**
*
* @return
*/
public long getLastModified() {
return this.mLastModified;
}
/**
*
* @return
*/
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -1,256 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
/**
* SqlData
* SQLiteJSON
*
*/
public class SqlData {
// 日志标签
private static final String TAG = SqlData.class.getSimpleName();
// 无效ID常量
private static final int INVALID_ID = -99999;
/**
*
*
*/
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, // 数据ID
DataColumns.MIME_TYPE, // MIME类型
DataColumns.CONTENT, // 内容
DataColumns.DATA1, // 数据列1
DataColumns.DATA3 // 数据列3
};
// 数据ID列索引
public static final int DATA_ID_COLUMN = 0;
// MIME类型列索引
public static final int DATA_MIME_TYPE_COLUMN = 1;
// 内容列索引
public static final int DATA_CONTENT_COLUMN = 2;
// 数据列1索引
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
// 数据列3索引
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
// 内容解析器用于访问ContentProvider
private ContentResolver mContentResolver;
// 是否为新建数据记录
private boolean mIsCreate;
// 数据ID
private long mDataId;
// MIME类型
private String mDataMimeType;
// 内容
private String mDataContent;
// 数据列1的值
private long mDataContentData1;
// 数据列3的值
private String mDataContentData3;
// 存储有变动的数据值的ContentValues对象
private ContentValues mDiffDataValues;
/**
* -
* @param context
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true; // 标记为新记录
mDataId = INVALID_ID; // 初始化为无效ID
mDataMimeType = DataConstants.NOTE; // 默认MIME类型为笔记
mDataContent = ""; // 内容初始化为空字符串
mDataContentData1 = 0; // 数据列1初始化为0
mDataContentData3 = ""; // 数据列3初始化为空字符串
mDiffDataValues = new ContentValues(); // 初始化差异值容器
}
/**
* -
* @param context
* @param c
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为现有记录
loadFromCursor(c); // 从游标加载数据
mDiffDataValues = new ContentValues(); // 初始化差异值容器
}
/**
*
* @param c
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN); // 加载数据ID
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); // 加载MIME类型
mDataContent = c.getString(DATA_CONTENT_COLUMN); // 加载内容
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); // 加载数据列1
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 加载数据列3
}
/**
* JSON
* JSON
* @param js JSON
* @throws JSONException JSON
*/
public void setContent(JSONObject js) throws JSONException {
// 处理数据ID
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId); // 记录变化
}
mDataId = dataId; // 更新当前值
// 处理MIME类型
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 记录变化
}
mDataMimeType = dataMimeType; // 更新当前值
// 处理内容
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent); // 记录变化
}
mDataContent = dataContent; // 更新当前值
// 处理数据列1
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 记录变化
}
mDataContentData1 = dataContentData1; // 更新当前值
// 处理数据列3
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 记录变化
}
mDataContentData3 = dataContentData3; // 更新当前值
}
/**
* JSON
* @return JSON
* @throws JSONException JSON
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId); // 添加数据ID
js.put(DataColumns.MIME_TYPE, mDataMimeType); // 添加MIME类型
js.put(DataColumns.CONTENT, mDataContent); // 添加内容
js.put(DataColumns.DATA1, mDataContentData1); // 添加数据列1
js.put(DataColumns.DATA3, mDataContentData3); // 添加数据列3
return js;
}
/**
*
*
* @param noteId ID
* @param validateVersion
* @param version
*/
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
// 新记录:插入操作
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID); // 移除无效ID
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 添加笔记ID
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); // 插入数据
try {
// 从返回的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)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
// 重置状态
mDiffDataValues.clear(); // 清空差异值
mIsCreate = false; // 标记为现有记录
}
/**
* ID
* @return ID
*/
public long getId() {
return mDataId;
}
}

@ -1,635 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
/**
* SqlNote
* SQLiteJSON
*
*/
public class SqlNote {
// 日志标签
private static final String TAG = SqlNote.class.getSimpleName();
// 无效ID常量
private static final int INVALID_ID = -99999;
/**
*
*
*/
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, // 笔记ID
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.CREATED_DATE, // 创建日期
NoteColumns.HAS_ATTACHMENT, // 是否有附件
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.NOTES_COUNT, // 笔记数量(文件夹用)
NoteColumns.PARENT_ID, // 父节点ID
NoteColumns.SNIPPET, // 内容摘要
NoteColumns.TYPE, // 类型(笔记/文件夹/系统)
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.SYNC_ID, // 同步ID
NoteColumns.LOCAL_MODIFIED, // 本地修改标志
NoteColumns.ORIGIN_PARENT_ID, // 原始父节点ID
NoteColumns.GTASK_ID, // Google Task ID
NoteColumns.VERSION // 版本号
};
// 笔记ID列索引
public static final int ID_COLUMN = 0;
// 提醒日期列索引
public static final int ALERTED_DATE_COLUMN = 1;
// 背景颜色ID列索引
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;
// 父节点ID列索引
public static final int PARENT_ID_COLUMN = 7;
// 内容摘要列索引
public static final int SNIPPET_COLUMN = 8;
// 类型列索引
public static final int TYPE_COLUMN = 9;
// 小部件ID列索引
public static final int WIDGET_ID_COLUMN = 10;
// 小部件类型列索引
public static final int WIDGET_TYPE_COLUMN = 11;
// 同步ID列索引
public static final int SYNC_ID_COLUMN = 12;
// 本地修改标志列索引
public static final int LOCAL_MODIFIED_COLUMN = 13;
// 原始父节点ID列索引
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
// Google Task ID列索引
public static final int GTASK_ID_COLUMN = 15;
// 版本号列索引
public static final int VERSION_COLUMN = 16;
// 上下文对象
private Context mContext;
// 内容解析器
private ContentResolver mContentResolver;
// 是否为新建笔记
private boolean mIsCreate;
// 笔记ID
private long mId;
// 提醒日期
private long mAlertDate;
// 背景颜色ID
private int mBgColorId;
// 创建日期
private long mCreatedDate;
// 是否有附件0:无, 1:有)
private int mHasAttachment;
// 修改日期
private long mModifiedDate;
// 父节点ID
private long mParentId;
// 内容摘要
private String mSnippet;
// 笔记类型
private int mType;
// 小部件ID
private int mWidgetId;
// 小部件类型
private int mWidgetType;
// 原始父节点ID用于恢复
private long mOriginParent;
// 版本号
private long mVersion;
// 存储有变动的笔记值的ContentValues对象
private ContentValues mDiffNoteValues;
// 关联的数据列表
private ArrayList<SqlData> mDataList;
/**
* -
* @param context
*/
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true; // 标记为新笔记
mId = INVALID_ID; // 初始化为无效ID
mAlertDate = 0; // 默认无提醒
mBgColorId = ResourceParser.getDefaultBgId(context); // 默认背景颜色
mCreatedDate = System.currentTimeMillis(); // 创建时间为当前时间
mHasAttachment = 0; // 默认无附件
mModifiedDate = System.currentTimeMillis(); // 修改时间为当前时间
mParentId = 0; // 默认父节点为根节点
mSnippet = ""; // 内容摘要为空
mType = Notes.TYPE_NOTE; // 默认为笔记类型
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // 默认无效小部件ID
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 默认无效小部件类型
mOriginParent = 0; // 默认无原始父节点
mVersion = 0; // 初始版本号为0
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
}
/**
* -
* @param context
* @param c
*/
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为现有笔记
loadFromCursor(c); // 从游标加载笔记信息
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 如果是笔记类型,加载关联的数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
}
/**
* - ID
* @param context
* @param id ID
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为现有笔记
loadFromCursor(id); // 根据ID加载笔记信息
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 如果是笔记类型,加载关联的数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
}
/**
* ID
* @param id ID
*/
private void loadFromCursor(long id) {
Cursor c = null;
try {
// 查询指定ID的笔记
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext(); // 移动到第一条记录
loadFromCursor(c); // 从游标加载数据
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally {
if (c != null)
c.close(); // 确保关闭游标
}
}
/**
*
* @param c
*/
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;
mDataList.clear(); // 清空现有数据列表
try {
// 查询关联的数据记录
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
// 遍历所有数据记录
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c); // 创建SqlData对象
mDataList.add(data); // 添加到数据列表
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close(); // 确保关闭游标
}
}
/**
* JSON
* JSON
* @param js JSON
* @return
*/
public boolean setContent(JSONObject js) {
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记信息
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder"); // 不能设置系统文件夹
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// 处理文件夹:只能更新摘要和类型
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); // 记录变化
}
mSnippet = snippet; // 更新当前值
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type); // 记录变化
}
mType = type; // 更新当前值
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
// 处理笔记:更新所有字段
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
// 处理笔记ID
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; // 更新当前值
// 处理父节点ID
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; // 更新当前值
// 处理原始父节点ID
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;
// 根据数据ID查找现有的SqlData对象
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp; // 找到现有对象
break;
}
}
}
// 如果没找到创建新的SqlData对象
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData); // 添加到数据列表
}
sqlData.setContent(data); // 设置数据内容
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
}
return true;
}
/**
* JSON
* @return JSON
*/
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) {
// 处理笔记类型:包含所有字段
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
note.put(NoteColumns.CREATED_DATE, mCreatedDate);
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
note.put(NoteColumns.PARENT_ID, mParentId);
note.put(NoteColumns.SNIPPET, mSnippet);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 添加笔记信息
// 添加关联数据
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data); // 添加到数据数组
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 添加数据信息
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
// 处理文件夹或系统类型:只包含必要字段
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 添加笔记信息
}
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null;
}
/**
* ID
* @param id ID
*/
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 记录变化
}
/**
* Google Task ID
* @param gid Google Task ID
*/
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 记录变化
}
/**
* ID
* @param syncId ID
*/
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 记录变化
}
/**
*
* 0
*/
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 记录变化
}
/**
* ID
* @return ID
*/
public long getId() {
return mId;
}
/**
* ID
* @return ID
*/
public long getParentId() {
return mParentId;
}
/**
*
* @return
*/
public String getSnippet() {
return mSnippet;
}
/**
*
* @return
*/
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
/**
*
*
* @param validateVersion
*/
public void commit(boolean validateVersion) {
if (mIsCreate) {
// 新笔记:插入操作
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID); // 移除无效ID
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); // 插入笔记
try {
// 从返回的URI中提取新笔记的ID
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}
// 如果是笔记类型,提交关联的数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1); // 提交数据,不验证版本
}
}
} else {
// 现有笔记:更新操作
// 验证笔记ID系统文件夹除外
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
if (mDiffNoteValues.size() > 0) {
mVersion ++; // 增加版本号
int result = 0;
if (!validateVersion) {
// 不验证版本号:直接更新
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
// 验证版本号:确保版本匹配
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
// 如果是笔记类型,提交关联的数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion); // 提交数据,验证版本
}
}
}
// 刷新本地信息
loadFromCursor(mId); // 重新从数据库加载笔记信息
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 重新加载关联数据
// 重置状态
mDiffNoteValues.clear(); // 清空差异值
mIsCreate = false; // 标记为现有笔记
}
}

@ -1,416 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.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;
/**
* Task
* Google Task
* Node
*/
public class Task extends Node {
// 日志标签
private static final String TAG = Task.class.getSimpleName();
// 任务是否已完成
private boolean mCompleted;
// 任务备注/笔记内容
private String mNotes;
// 元信息JSON对象包含本地数据库信息
private JSONObject mMetaInfo;
// 前一个兄弟任务(用于保持任务顺序)
private Task mPriorSibling;
// 父任务列表
private TaskList mParent;
/**
*
*
*/
public Task() {
super();
mCompleted = false; // 默认未完成
mNotes = null; // 默认无备注
mPriorSibling = null; // 默认无前兄弟
mParent = null; // 默认无父列表
mMetaInfo = null; // 默认无元信息
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 动作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 索引位置(在父列表中的位置)
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// 实体增量(包含任务信息)
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 任务名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 实体类型:任务
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 任务备注
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// 父节点ID
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// 目标父节点类型
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// 前兄弟节点ID用于确定插入位置
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
}
return js;
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 动作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 任务ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 实体增量(包含更新的任务信息)
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 任务名称
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 任务备注
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 删除标志
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-update jsonobject");
}
return js;
}
/**
* JSON
* Google TasksJSON
* @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));
}
// 任务备注
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// 删除标志
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// 完成状态
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get task content from jsonobject");
}
}
}
/**
* JSON
* JSON
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
Log.w(TAG, "setContentByLocalJSON: 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();
}
}
/**
* JSON
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
// 从Web创建的新任务无元信息
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
// 创建新的JSON结构
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name); // 笔记内容
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;
}
}
/**
*
* MetaData
* @param metaData MetaData
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
// 从备注中解析元信息JSON
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null; // 解析失败时设置为null
}
}
}
/**
*
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
// 从元信息中获取笔记信息
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE; // 元信息被删除,需要更新远程
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL; // 远程笔记ID被删除需要更新本地
}
// 验证笔记ID是否匹配
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL; // 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 {
// 有本地更新
// 验证Google Task ID是否匹配
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR; // 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
*/
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
// 以下为属性的getter和setter方法
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
public void setNotes(String notes) {
this.mNotes = notes;
}
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
public void setParent(TaskList parent) {
this.mParent = parent;
}
public boolean getCompleted() {
return this.mCompleted;
}
public String getNotes() {
return this.mNotes;
}
public Task getPriorSibling() {
return this.mPriorSibling;
}
public TaskList getParent() {
return this.mParent;
}
}

@ -1,450 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
/**
* TaskList
* Google Task
* NodeTask
*/
public class TaskList extends Node {
// 日志标签
private static final String TAG = TaskList.class.getSimpleName();
// 索引位置(在父节点中的位置)
private int mIndex;
// 子任务列表
private ArrayList<Task> mChildren;
/**
*
*
*/
public TaskList() {
super();
mChildren = new ArrayList<Task>(); // 初始化子任务列表
mIndex = 1; // 默认索引为1
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 动作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 索引位置
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// 实体增量(包含任务列表信息)
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 列表名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 实体类型:组/列表
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
}
return js;
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 动作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 列表ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 实体增量(包含更新的列表信息)
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 列表名称
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 删除标志
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
}
return js;
}
/**
* JSON
* Google TasksJSON
* @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
* JSON
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
// 解析文件夹信息
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 根据文件夹类型设置名称
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// 普通文件夹
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); // 添加MIUI前缀
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
// 系统文件夹
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); // 默认文件夹
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE); // 通话记录文件夹
else
Log.e(TAG, "invalid system folder"); // 无效的系统文件夹
} else {
Log.e(TAG, "error type"); // 错误类型
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
/**
* JSON
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
// 处理文件夹名称移除MIUI前缀
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); // 添加到JSON
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
/**
*
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
// 检查本地修改标志
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 无本地更新
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 两端均无更新
return SYNC_ACTION_NONE;
} else {
// 将远程更新应用到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// 有本地更新
// 验证Google Task ID是否匹配
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR; // ID不匹配返回错误
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 只有本地有修改
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// 对于文件夹冲突,优先应用本地修改
return SYNC_ACTION_UPDATE_REMOTE;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR; // 发生异常,返回错误
}
/**
*
* @return
*/
public int getChildTaskCount() {
return mChildren.size();
}
/**
*
* @param task
* @return
*/
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;
}
/**
*
* @param task
* @param index
* @return
*/
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;
}
/**
*
* @param task
* @return
*/
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
*/
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
}
int pos = mChildren.indexOf(task);
if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list");
return false;
}
if (pos == index) // 位置未变
return true;
// 先移除再添加到新位置
return (removeChildTask(task) && addChildTask(task, index));
}
/**
* Google Task ID
* @param gid Google Task ID
* @return null
*/
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
if (t.getGid().equals(gid)) {
return t;
}
}
return null;
}
/**
*
* @param task
* @return -1
*/
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
/**
*
* @param index
* @return null
*/
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
return null;
}
return mChildren.get(index);
}
/**
* Google Task ID
* @param gid Google Task ID
* @return null
*/
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))
return task;
}
return null;
}
/**
*
* @return
*/
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
}
// 以下为属性的getter和setter方法
public void setIndex(int index) {
this.mIndex = index;
}
public int getIndex() {
return this.mIndex;
}
}

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

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

@ -1,145 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
public interface OnCompleteListener {
void onComplete();
}
private Context mContext;
private NotificationManager mNotifiManager;
private GTaskManager mTaskManager;
private OnCompleteListener mOnCompleteListener;
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
mTaskManager = GTaskManager.getInstance();
}
public void cancelSync() {
mTaskManager.cancelSync();
}
public void publishProgess(String message) {
publishProgress(new String[] {
message
});
}
// private void showNotification(int tickerId, String content) {
// Notification notification = new Notification(R.drawable.notification, mContext
// .getString(tickerId), System.currentTimeMillis());
// notification.defaults = Notification.DEFAULT_LIGHTS;
// notification.flags = Notification.FLAG_AUTO_CANCEL;
// PendingIntent pendingIntent;
// if (tickerId != R.string.ticker_success) {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesPreferenceActivity.class), 0);
//
// } else {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesListActivity.class), 0);
// }
// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
// pendingIntent);
// mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// }
private void showNotification(int tickerId, String content) {
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
}
Notification.Builder builder = new Notification.Builder(mContext)
.setAutoCancel(true)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(content)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true);
Notification notification=builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
@Override
protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
return mTaskManager.sync(mContext, this);
}
@Override
protected void onProgressUpdate(String... progress) {
showNotification(R.string.ticker_syncing, progress[0]);
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
@Override
protected void onPostExecute(Integer result) {
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -1,585 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName();
private static final String GTASK_URL = "https://mail.google.com/tasks/";
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
private static GTaskClient mInstance = null;
private DefaultHttpClient mHttpClient;
private String mGetUrl;
private String mPostUrl;
private long mClientVersion;
private boolean mLoggedin;
private long mLastLoginTime;
private int mActionId;
private Account mAccount;
private JSONArray mUpdateArray;
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// need to re-login after account switch
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
mLastLoginTime = System.currentTimeMillis();
String authToken = loginGoogleAccount(activity, false);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// login with custom domain if necessary
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// try to login with google official url
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true;
return true;
}
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google");
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
return null;
}
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
break;
}
}
if (account != null) {
mAccount = account;
} else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// get the token now
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken;
}
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
// token and try again
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true;
}
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the cookie now
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// get the client version
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
}
return true;
}
private int getActionId() {
return mActionId++;
}
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1");
return httpPost;
}
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
InputStream input = entity.getContent();
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
}
try {
InputStreamReader isr = new InputStreamReader(input);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
while (true) {
String buff = br.readLine();
if (buff == null) {
return sb.toString();
}
sb = sb.append(buff);
}
} finally {
input.close();
}
}
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
HttpPost httpPost = createHttpPost();
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// execute the post
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("unable to convert response content to jsonobject");
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request");
}
}
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
try {
JSONObject jsPost = new JSONObject();
// action_list
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("commit update: handing jsonobject failed");
}
}
}
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// too many update items may result in an error
// set max to 10 items
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and
// it is not the first one
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
if (preParent != curParent) {
// put the dest_list only if moving between tasklists
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed");
}
}
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("delete node: handing jsonobject failed");
}
}
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
try {
HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the task list
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task list: handing jsonobject failed");
}
}
public Account getSyncAccount() {
return mAccount;
}
public void resetUpdateArray() {
mUpdateArray = null;
}
}

@ -1,800 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName();
public static final int STATE_SUCCESS = 0;
public static final int STATE_NETWORK_ERROR = 1;
public static final int STATE_INTERNAL_ERROR = 2;
public static final int STATE_SYNC_IN_PROGRESS = 3;
public static final int STATE_SYNC_CANCELLED = 4;
private static GTaskManager mInstance = null;
private Activity mActivity;
private Context mContext;
private ContentResolver mContentResolver;
private boolean mSyncing;
private boolean mCancelled;
private HashMap<String, TaskList> mGTaskListHashMap;
private HashMap<String, Node> mGTaskHashMap;
private HashMap<String, MetaData> mMetaHashMap;
private TaskList mMetaList;
private HashSet<Long> mLocalDeleteIdMap;
private HashMap<String, Long> mGidToNid;
private HashMap<Long, String> mNidToGid;
private GTaskManager() {
mSyncing = false;
mCancelled = false;
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// login google task
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// get the task list from google
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// do content sync work
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
JSONArray jsTaskLists = client.getTaskLists();
// init meta list first
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// load meta data
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);
}
}
}
}
}
// create meta list if not existed
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// init task list
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) {
TaskList tasklist = new TaskList();
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// load tasks
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid));
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
mLocalDeleteIdMap.clear();
if (mCancelled) {
return;
}
// for local deleted note
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_FOLDER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// sync folder first
syncFolder();
// for note existing in database
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_FOLDER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing note in database");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// go through remaining items
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// mCancelled can be set by another thread, so we neet to check one by
// one
// clear local delete table
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// refresh local sync id
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
if (mCancelled) {
return;
}
// for root folder
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null) {
c.moveToNext();
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// for system folder, only update remote name if necessary
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
} else {
Log.w(TAG, "failed to query root folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for call-note folder
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// for system folder, only update remote name if
// necessary
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
}
} else {
Log.w(TAG, "failed to query call note folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for local existing folders
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_FOLDER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for remote add folders
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
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:
// merging both modifications maybe a good idea
// right now just use local update simply
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
break;
case Node.SYNC_ACTION_ERROR:
default:
throw new ActionFailureException("unkown sync action type");
}
}
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
if (node instanceof TaskList) {
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else {
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// the id is not available, have to create a new one
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)) {
// the data id is not available, have to create
// a new one
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js);
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
sqlNote.setParentId(parentId.longValue());
}
// create the local node
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
}
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// update the note locally
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// update meta info
updateRemoteMeta(node.getGid(), sqlNote);
}
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// update remotely
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task");
}
mGTaskListHashMap.get(parentGid).addChildTask(task);
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// add meta
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// 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();
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;
}
}
// 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;
}
// update local note
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id mapping
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
// update remotely
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
// move task if necessary
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);
}
}
// clear local modified flag
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.getInstance().addUpdateNode(metaData);
} else {
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
mMetaHashMap.put(gid, metaData);
GTaskClient.getInstance().createTask(metaData);
}
}
}
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// get the latest gtask list
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_FOLDER)
}, 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;
}
}
}
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
mCancelled = true;
}
}

@ -1,128 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
public class GTaskSyncService extends Service {
public final static String ACTION_STRING_NAME = "sync_action_type";
public final static int ACTION_START_SYNC = 0;
public final static int ACTION_CANCEL_SYNC = 1;
public final static int ACTION_INVALID = 2;
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
private static GTaskASyncTask mSyncTask = null;
private static String mSyncProgress = "";
private void startSync() {
if (mSyncTask == null) {
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
public void onComplete() {
mSyncTask = null;
sendBroadcast("");
stopSelf();
}
});
sendBroadcast("");
mSyncTask.execute();
}
}
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
@Override
public void onCreate() {
mSyncTask = null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
startSync();
break;
case ACTION_CANCEL_SYNC:
cancelSync();
break;
default:
break;
}
return START_STICKY;
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
public IBinder onBind(Intent intent) {
return null;
}
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
sendBroadcast(intent);
}
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
public static boolean isSyncing() {
return mSyncTask != null;
}
public static String getProgressString() {
return mSyncProgress;
}
}

@ -1,253 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
public class Note {
private ContentValues mNoteDiffValues;
private NoteData mNoteData;
private static final String TAG = "Note";
/**
* Create a new note id for adding a new note to databases
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.PARENT_ID, folderId);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
public long getTextDataId() {
return mNoteData.mTextDataId;
}
public void setCallDataId(long id) {
mNoteData.setCallDataId(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);
}
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
mNoteDiffValues.clear();
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
}
return true;
}
private class NoteData {
private long mTextDataId;
private ContentValues mTextDataValues;
private long mCallDataId;
private ContentValues mCallDataValues;
private static final String TAG = "NoteData";
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
}
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
mTextDataValues.clear();
}
if(mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear();
}
if (operationList.size() > 0) {
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
return null;
}
}
}

@ -1,368 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote {
// Note for the working note
private Note mNote;
// Note Id
private long mNoteId;
// Note content
private String mContent;
// Note mode
private int mMode;
private long mAlertDate;
private long mModifiedDate;
private int mBgColorId;
private int mWidgetId;
private int mWidgetType;
private long mFolderId;
private Context mContext;
private static final String TAG = "WorkingNote";
private boolean mIsDeleted;
private NoteSettingChangedListener mNoteSettingStatusListener;
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);
}
}

@ -1,344 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// 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_FOLDER + ") 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;
}
}

@ -1,504 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.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;
}
int deletedCount = 0;
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
// 逐个删除,确保每个便签都被正确删除
int count = resolver.delete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), null, null);
if (count > 0) {
deletedCount++;
}
}
return deletedCount > 0;
}
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) {
// 如果目标是回收站保存原始父目录ID
if (folderId == Notes.ID_TRASH_FOLDER) {
// 查询当前父目录ID
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
new String[] { NoteColumns.PARENT_ID }, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
long currentParentId = cursor.getLong(0);
cursor.close();
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.ORIGIN_PARENT_ID, currentParentId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
} else if (cursor != null) {
cursor.close();
}
} else {
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);
return results != null && results.length > 0;
} 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_FOLDER)},
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_FOLDER,
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_FOLDER +
" 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;
}
/**
*
*
* 1.
* 2.
* 3.
* 4.
* @param text
* @return
*/
public static int countWords(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
int count = 0;
int length = text.length();
boolean inEnglishWord = false;
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
if (isChineseChar(c) || isDigit(c)) {
// 中文字符或数字,直接计数
count++;
inEnglishWord = false;
} else if (isEnglishLetter(c)) {
// 英文字符
if (!inEnglishWord) {
// 新的英文单词开始
count++;
inEnglishWord = true;
}
// 同一个英文单词的后续字符,不重复计数
} else {
// 其他字符(标点符号、空格等),不计数
inEnglishWord = false;
}
}
return count;
}
/**
*
* @param c
* @return
*/
private static boolean isEnglishLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
/**
*
* @param c
* @return
*/
private static boolean isChineseChar(char c) {
return c >= '\u4e00' && c <= '\u9fa5';
}
/**
*
* @param c
* @return
*/
private static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
/**
* 便
* @param resolver
* @param ids 便ID
* @return
*/
public static boolean batchRestoreNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null || ids.size() == 0) {
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
// 查询便签的原始父目录ID
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
new String[] { NoteColumns.ORIGIN_PARENT_ID }, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
long originParentId = cursor.getLong(0);
cursor.close();
// 恢复便签到原目录
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, originParentId);
builder.withValue(NoteColumns.ORIGIN_PARENT_ID, 0);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
} else if (cursor != null) {
cursor.close();
}
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
return results != null && results.length > 0;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
/**
* 便
* @param resolver
* @param ids 便ID
* @return
*/
public static boolean batchRestoreNotes(ContentResolver resolver, long[] ids) {
if (ids == null || ids.length == 0) {
return true;
}
HashSet<Long> idSet = new HashSet<Long>();
for (long id : ids) {
idSet.add(id);
}
return batchDeleteNotes(resolver, idSet);
}
/**
* 便
* @param resolver
* @param ids 便ID
* @return
*/
public static boolean batchPermanentlyDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
return batchDeleteNotes(resolver, ids);
}
/**
* 便
* @param resolver
* @param ids 便ID
* @return
*/
public static boolean batchPermanentlyDeleteNotes(ContentResolver resolver, long[] ids) {
if (ids == null || ids.length == 0) {
return true;
}
HashSet<Long> idSet = new HashSet<Long>();
for (long id : ids) {
idSet.add(id);
}
return batchDeleteNotes(resolver, idSet);
}
/**
* 便
* @param resolver
* @param days
* @return 便
*/
public static int cleanupTrashNotes(ContentResolver resolver, int days) {
long cutoffTime = System.currentTimeMillis() - (days * 24 * 60 * 60 * 1000);
// 查询超过指定天数的回收站便签
String selection = NoteColumns.PARENT_ID + " = ? AND " + NoteColumns.MODIFIED_DATE + " < ?";
String[] selectionArgs = new String[] {
String.valueOf(Notes.ID_TRASH_FOLDER),
String.valueOf(cutoffTime)
};
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.ID },
selection, selectionArgs, null);
int deletedCount = 0;
if (cursor != null) {
HashSet<Long> ids = new HashSet<Long>();
while (cursor.moveToNext()) {
ids.add(cursor.getLong(0));
}
cursor.close();
if (ids.size() > 0) {
if (batchDeleteNotes(resolver, ids)) {
deletedCount = ids.size();
}
}
}
return deletedCount;
}
}

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

@ -1,283 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.ContentResolver;
import android.content.ContentValues;
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.PasswordNote;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
*
*/
public class PasswordUtils {
private static final String TAG = "PasswordUtils";
/**
*
* @param password
* @return
*/
public static String encryptPassword(String password) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "Failed to encrypt password", e);
return password; // 加密失败时返回原始密码
}
}
/**
*
* @param contentResolver
* @param folderId ID
* @param passwordType
* @param password
* @return
*/
public static boolean saveFolderPassword(ContentResolver contentResolver, long folderId, int passwordType, String password) {
// 先删除现有的密码记录
deleteFolderPassword(contentResolver, folderId);
// 如果是无密码类型,直接返回成功
if (passwordType == Notes.PASSWORD_TYPE_NONE) {
return true;
}
// 创建新的密码记录
ContentValues values = new ContentValues();
values.put(DataColumns.NOTE_ID, folderId);
values.put(DataColumns.MIME_TYPE, Notes.DataConstants.PASSWORD);
values.put(PasswordNote.PASSWORD_TYPE, passwordType);
values.put(PasswordNote.PASSWORD_CONTENT, encryptPassword(password));
Uri uri = contentResolver.insert(Notes.CONTENT_DATA_URI, values);
return uri != null;
}
/**
*
* @param contentResolver
* @param noteId ID
* @param passwordType
* @param password
* @return
*/
public static boolean saveNotePassword(ContentResolver contentResolver, long noteId, int passwordType, String password) {
// 先删除现有的密码记录
deleteNotePassword(contentResolver, noteId);
// 如果是无密码类型,直接返回成功
if (passwordType == Notes.PASSWORD_TYPE_NONE) {
return true;
}
// 创建新的密码记录
ContentValues values = new ContentValues();
values.put(DataColumns.NOTE_ID, noteId);
values.put(DataColumns.MIME_TYPE, Notes.DataConstants.PASSWORD);
values.put(PasswordNote.PASSWORD_TYPE, passwordType);
values.put(PasswordNote.PASSWORD_CONTENT, encryptPassword(password));
Uri uri = contentResolver.insert(Notes.CONTENT_DATA_URI, values);
return uri != null;
}
/**
*
* @param contentResolver
* @param folderId ID
* @return
*/
public static boolean deleteFolderPassword(ContentResolver contentResolver, long folderId) {
String selection = DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?";
String[] selectionArgs = new String[]{String.valueOf(folderId), Notes.DataConstants.PASSWORD};
int rows = contentResolver.delete(Notes.CONTENT_DATA_URI, selection, selectionArgs);
return rows > 0;
}
/**
*
* @param contentResolver
* @param noteId ID
* @return
*/
public static boolean deleteNotePassword(ContentResolver contentResolver, long noteId) {
String selection = DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?";
String[] selectionArgs = new String[]{String.valueOf(noteId), Notes.DataConstants.PASSWORD};
int rows = contentResolver.delete(Notes.CONTENT_DATA_URI, selection, selectionArgs);
return rows > 0;
}
/**
*
* @param contentResolver
* @param folderId ID
* @return
*/
public static String[] getFolderPasswordInfo(ContentResolver contentResolver, long folderId) {
String selection = DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?";
String[] selectionArgs = new String[]{String.valueOf(folderId), Notes.DataConstants.PASSWORD};
String[] projection = new String[]{PasswordNote.PASSWORD_TYPE, PasswordNote.PASSWORD_CONTENT};
Cursor cursor = contentResolver.query(Notes.CONTENT_DATA_URI, projection, selection, selectionArgs, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
int passwordType = cursor.getInt(0);
String password = cursor.getString(1);
return new String[]{String.valueOf(passwordType), password};
}
} finally {
cursor.close();
}
}
return new String[]{String.valueOf(Notes.PASSWORD_TYPE_NONE), ""};
}
/**
*
* @param contentResolver
* @param noteId ID
* @return
*/
public static String[] getNotePasswordInfo(ContentResolver contentResolver, long noteId) {
String selection = DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?";
String[] selectionArgs = new String[]{String.valueOf(noteId), Notes.DataConstants.PASSWORD};
String[] projection = new String[]{PasswordNote.PASSWORD_TYPE, PasswordNote.PASSWORD_CONTENT};
Cursor cursor = contentResolver.query(Notes.CONTENT_DATA_URI, projection, selection, selectionArgs, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
int passwordType = cursor.getInt(0);
String password = cursor.getString(1);
return new String[]{String.valueOf(passwordType), password};
}
} finally {
cursor.close();
}
}
return new String[]{String.valueOf(Notes.PASSWORD_TYPE_NONE), ""};
}
/**
*
* @param contentResolver
* @param folderId ID
* @param password
* @return
*/
public static boolean verifyFolderPassword(ContentResolver contentResolver, long folderId, String password) {
String[] passwordInfo = getFolderPasswordInfo(contentResolver, folderId);
int passwordType = Integer.parseInt(passwordInfo[0]);
String storedPassword = passwordInfo[1];
// 如果是无密码类型,直接返回成功
if (passwordType == Notes.PASSWORD_TYPE_NONE) {
return true;
}
// 验证密码
String encryptedPassword = encryptPassword(password);
return encryptedPassword.equals(storedPassword);
}
/**
*
* @param contentResolver
* @param noteId ID
* @param password
* @return
*/
public static boolean verifyNotePassword(ContentResolver contentResolver, long noteId, String password) {
String[] passwordInfo = getNotePasswordInfo(contentResolver, noteId);
int passwordType = Integer.parseInt(passwordInfo[0]);
String storedPassword = passwordInfo[1];
// 如果是无密码类型,直接返回成功
if (passwordType == Notes.PASSWORD_TYPE_NONE) {
return true;
}
// 验证密码
String encryptedPassword = encryptPassword(password);
return encryptedPassword.equals(storedPassword);
}
/**
*
* @param contentResolver
* @param folderId ID
* @return
*/
public static boolean isFolderPasswordSet(ContentResolver contentResolver, long folderId) {
String[] passwordInfo = getFolderPasswordInfo(contentResolver, folderId);
int passwordType = Integer.parseInt(passwordInfo[0]);
return passwordType != Notes.PASSWORD_TYPE_NONE;
}
/**
*
* @param contentResolver
* @param noteId ID
* @return
*/
public static boolean isNotePasswordSet(ContentResolver contentResolver, long noteId) {
String[] passwordInfo = getNotePasswordInfo(contentResolver, noteId);
int passwordType = Integer.parseInt(passwordInfo[0]);
return passwordType != Notes.PASSWORD_TYPE_NONE;
}
/**
*
* @param contentResolver
* @param folderId ID
* @return
*/
public static int getFolderPasswordType(ContentResolver contentResolver, long folderId) {
String[] passwordInfo = getFolderPasswordInfo(contentResolver, folderId);
return Integer.parseInt(passwordInfo[0]);
}
/**
*
* @param contentResolver
* @param noteId ID
* @return
*/
public static int getNotePasswordType(ContentResolver contentResolver, long noteId) {
String[] passwordInfo = getNotePasswordInfo(contentResolver, noteId);
return Integer.parseInt(passwordInfo[0]);
}
}

@ -1,181 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
public static final int BG_DEFAULT_COLOR = YELLOW;
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
public static class NoteBgResources {
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
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;
}
}
}

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

@ -1,88 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
private static final int COLUMN_ID = 0;
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();
}
// 初始化回收站定时清理任务每天凌晨2点执行
setupTrashCleanupAlarm(context);
}
/**
*
* @param context
*/
private void setupTrashCleanupAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// 创建清理任务的Intent
Intent cleanupIntent = new Intent(context, TrashCleanupReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, cleanupIntent, 0);
// 计算明天凌晨2点的时间
long tomorrow = System.currentTimeMillis() + (24 * 60 * 60 * 1000);
long cleanupTime = tomorrow - (tomorrow % (24 * 60 * 60 * 1000)) + (2 * 60 * 60 * 1000);
// 设置每天重复执行的闹钟
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, cleanupTime,
AlarmManager.INTERVAL_DAY, pendingIntent);
}
}

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

@ -1,485 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
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());
}
}
}

@ -1,90 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
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());
}
}
}

@ -1,210 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
/**
*
*/
public class DigitPasswordDialog {
private Context mContext;
private boolean mIsSettingMode; // 是否为设置模式(需要两次输入确认)
private String mFirstPassword; // 第一次输入的密码
private OnPasswordSetListener mPasswordSetListener;
private OnPasswordVerifyListener mPasswordVerifyListener;
/**
*
*/
public interface OnPasswordSetListener {
void onPasswordSet(String password);
}
/**
*
*/
public interface OnPasswordVerifyListener {
void onPasswordVerify(String password);
}
/**
*
* @param context
* @param isSettingMode
*/
public DigitPasswordDialog(Context context, boolean isSettingMode) {
mContext = context;
mIsSettingMode = isSettingMode;
}
/**
*
* @param context
*/
public DigitPasswordDialog(Context context) {
mContext = context;
mIsSettingMode = false;
}
/**
*
* @param listener
*/
public void setOnPasswordSetListener(OnPasswordSetListener listener) {
mPasswordSetListener = listener;
}
/**
*
* @param listener
*/
public void setOnPasswordVerifyListener(OnPasswordVerifyListener listener) {
mPasswordVerifyListener = listener;
}
/**
*
*/
public void show() {
if (mIsSettingMode) {
showSettingDialog();
} else {
showVerifyDialog();
}
}
/**
*
*/
private void showSettingDialog() {
// 第一次输入密码
showPasswordInputDialog("设置数字密码", "请输入数字密码", new OnPasswordInputListener() {
@Override
public void onPasswordInput(String password) {
if (password != null) {
mFirstPassword = password;
// 第二次输入密码确认
showPasswordInputDialog("确认数字密码", "请再次输入数字密码", new OnPasswordInputListener() {
@Override
public void onPasswordInput(String password) {
if (password != null) {
if (password.equals(mFirstPassword)) {
// 两次输入一致,设置成功
if (mPasswordSetListener != null) {
mPasswordSetListener.onPasswordSet(password);
}
} else {
// 两次输入不一致,提示错误
Toast.makeText(mContext, "两次输入的密码不一致,请重新设置", Toast.LENGTH_SHORT).show();
// 重新开始设置
showSettingDialog();
}
}
}
});
}
}
});
}
/**
*
*/
private void showVerifyDialog() {
showPasswordInputDialog("输入数字密码", "请输入数字密码", new OnPasswordInputListener() {
@Override
public void onPasswordInput(String password) {
if (password != null && mPasswordVerifyListener != null) {
mPasswordVerifyListener.onPasswordVerify(password);
}
}
});
}
/**
*
* @param title
* @param message
* @param listener
*/
private void showPasswordInputDialog(String title, String message, final OnPasswordInputListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(title);
builder.setMessage(message);
// 创建密码输入框
final EditText passwordEditText = new EditText(mContext);
passwordEditText.setInputType(android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD);
passwordEditText.setHint("请输入数字密码");
builder.setView(passwordEditText);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordEditText.getText().toString().trim();
if (isValidPassword(password)) {
dialog.dismiss();
listener.onPasswordInput(password);
} else {
Toast.makeText(mContext, "密码不能为空且只能包含数字", Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
listener.onPasswordInput(null);
}
});
builder.show();
}
/**
*
* @param password
* @return
*/
private boolean isValidPassword(String password) {
if (password == null || password.isEmpty()) {
return false;
}
for (char c : password.toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
/**
*
*/
private interface OnPasswordInputListener {
void onPasswordInput(String password);
}
}

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

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

@ -1,217 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
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);
}
}

@ -1,232 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.STICKY,
};
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 static final int STICKY_COLUMN = 12;
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 mSticky;
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);
mSticky = cursor.getInt(STICKY_COLUMN) == 1;
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 boolean isSticky() {
return mSticky;
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -1,184 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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;
}
}
}
}

@ -1,135 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData;
private CheckBox mCheckBox;
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.isSticky()) {
// 置顶状态优先显示
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else 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()));
if (data.isSticky()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.isSticky()) {
// 置顶状态优先显示
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setBackground(data);
}
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
public NoteItemData getItemData() {
return mItemData;
}
}

@ -1,388 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
public class NotesPreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "notes_preferences";
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory;
private GTaskReceiver mReceiver;
private Account[] mOriAccounts;
private boolean mHasAddedAccount;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
/* 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;
}
}
}

@ -1,136 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.view.View;
import android.widget.Button;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.PasswordUtils;
/**
*
*/
public class PasswordSettingDialog {
private Context mContext;
private long mFolderId;
private OnPasswordSetListener mListener;
/**
*
*/
public interface OnPasswordSetListener {
void onPasswordSet(boolean success);
}
/**
*
* @param context
* @param noteId ID
* @param listener
*/
public PasswordSettingDialog(Context context, long noteId, OnPasswordSetListener listener) {
mContext = context;
mFolderId = noteId; // 复用mFolderId变量存储笔记ID
mListener = listener;
}
/**
*
*/
public void show() {
// 创建密码类型选择对话框
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle("选择密码类型");
builder.setItems(new String[]{"数字密码", "九宫格图案密码", "无密码"},
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
switch (which) {
case 0: // 数字密码
showDigitPasswordDialog();
break;
case 1: // 九宫格图案密码
showPatternPasswordDialog();
break;
case 2: // 无密码
setNoPassword();
break;
}
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
/**
*
*/
private void showDigitPasswordDialog() {
final DigitPasswordDialog dialog = new DigitPasswordDialog(mContext, true);
dialog.setOnPasswordSetListener(new DigitPasswordDialog.OnPasswordSetListener() {
@Override
public void onPasswordSet(String password) {
if (password != null) {
boolean success = PasswordUtils.saveNotePassword(
mContext.getContentResolver(), mFolderId, Notes.PASSWORD_TYPE_DIGIT, password);
if (mListener != null) {
mListener.onPasswordSet(success);
}
}
}
});
dialog.show();
}
/**
*
*/
private void showPatternPasswordDialog() {
final PatternPasswordDialog dialog = new PatternPasswordDialog(mContext, true);
dialog.setOnPasswordSetListener(new PatternPasswordDialog.OnPasswordSetListener() {
@Override
public void onPasswordSet(String pattern) {
if (pattern != null) {
boolean success = PasswordUtils.saveNotePassword(
mContext.getContentResolver(), mFolderId, Notes.PASSWORD_TYPE_PATTERN, pattern);
if (mListener != null) {
mListener.onPasswordSet(success);
}
}
}
});
dialog.show();
}
/**
*
*/
private void setNoPassword() {
boolean success = PasswordUtils.saveNotePassword(
mContext.getContentResolver(), mFolderId, Notes.PASSWORD_TYPE_NONE, "");
if (mListener != null) {
mListener.onPasswordSet(success);
}
}
}

@ -1,359 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class PatternPasswordDialog {
private Context mContext;
private boolean mIsSettingMode; // 是否为设置模式(需要两次输入确认)
private String mFirstPattern; // 第一次输入的图案
private OnPasswordSetListener mPasswordSetListener;
private OnPasswordVerifyListener mPasswordVerifyListener;
/**
*
*/
public interface OnPasswordSetListener {
void onPasswordSet(String pattern);
}
/**
*
*/
public interface OnPasswordVerifyListener {
void onPasswordVerify(String pattern);
}
/**
*
* @param context
* @param isSettingMode
*/
public PatternPasswordDialog(Context context, boolean isSettingMode) {
mContext = context;
mIsSettingMode = isSettingMode;
}
/**
*
* @param context
*/
public PatternPasswordDialog(Context context) {
mContext = context;
mIsSettingMode = false;
}
/**
*
* @param listener
*/
public void setOnPasswordSetListener(OnPasswordSetListener listener) {
mPasswordSetListener = listener;
}
/**
*
* @param listener
*/
public void setOnPasswordVerifyListener(OnPasswordVerifyListener listener) {
mPasswordVerifyListener = listener;
}
/**
*
*/
public void show() {
if (mIsSettingMode) {
showSettingDialog();
} else {
showVerifyDialog();
}
}
/**
*
*/
private void showSettingDialog() {
// 第一次输入图案
showPatternInputDialog("设置图案密码", "请绘制图案密码", new OnPatternInputListener() {
@Override
public void onPatternInput(String pattern) {
if (pattern != null) {
mFirstPattern = pattern;
// 第二次输入图案确认
showPatternInputDialog("确认图案密码", "请再次绘制图案密码", new OnPatternInputListener() {
@Override
public void onPatternInput(String pattern) {
if (pattern != null) {
if (pattern.equals(mFirstPattern)) {
// 两次输入一致,设置成功
if (mPasswordSetListener != null) {
mPasswordSetListener.onPasswordSet(pattern);
}
} else {
// 两次输入不一致,提示错误
Toast.makeText(mContext, "两次绘制的图案不一致,请重新设置", Toast.LENGTH_SHORT).show();
// 重新开始设置
showSettingDialog();
}
}
}
});
}
}
});
}
/**
*
*/
private void showVerifyDialog() {
showPatternInputDialog("输入图案密码", "请绘制图案密码", new OnPatternInputListener() {
@Override
public void onPatternInput(String pattern) {
if (pattern != null && mPasswordVerifyListener != null) {
mPasswordVerifyListener.onPasswordVerify(pattern);
}
}
});
}
/**
*
* @param title
* @param message
* @param listener
*/
private void showPatternInputDialog(String title, String message, final OnPatternInputListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(title);
builder.setMessage(message);
// 创建图案输入视图
final PatternInputView patternView = new PatternInputView(mContext);
LinearLayout layout = new LinearLayout(mContext);
layout.setPadding(50, 50, 50, 50);
layout.addView(patternView);
builder.setView(layout);
final AlertDialog dialog = builder.create();
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
String pattern = patternView.getPattern();
if (isValidPattern(pattern)) {
dialog.dismiss();
listener.onPatternInput(pattern);
} else {
Toast.makeText(mContext, "图案密码不能为空且至少需要连接4个点", Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
dialog.dismiss();
listener.onPatternInput(null);
}
});
builder.show();
}
/**
*
* @param pattern
* @return
*/
private boolean isValidPattern(String pattern) {
return pattern != null && !pattern.isEmpty() && pattern.length() >= 4;
}
/**
*
*/
private interface OnPatternInputListener {
void onPatternInput(String pattern);
}
/**
*
*/
private static class PatternInputView extends View {
private static final int POINT_COUNT = 9;
private static final int POINT_RADIUS = 30;
private static final int LINE_WIDTH = 8;
private static final int POINT_SPACING = 100;
private Paint mPointPaint;
private Paint mLinePaint;
private Paint mSelectedPointPaint;
private Point[] mPoints;
private List<Point> mSelectedPoints;
private Path mPath;
private boolean mIsDrawing;
public PatternInputView(Context context) {
super(context);
init();
}
private void init() {
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPointPaint.setColor(Color.GRAY);
mPointPaint.setStyle(Paint.Style.FILL);
mSelectedPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSelectedPointPaint.setColor(Color.BLUE);
mSelectedPointPaint.setStyle(Paint.Style.FILL);
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setColor(Color.BLUE);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(LINE_WIDTH);
mPoints = new Point[POINT_COUNT];
mSelectedPoints = new ArrayList<>();
mPath = new Path();
mIsDrawing = false;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initPoints(w, h);
}
private void initPoints(int width, int height) {
int centerX = width / 2;
int centerY = height / 2;
// 计算9个点的位置
mPoints[0] = new Point(centerX - POINT_SPACING, centerY - POINT_SPACING);
mPoints[1] = new Point(centerX, centerY - POINT_SPACING);
mPoints[2] = new Point(centerX + POINT_SPACING, centerY - POINT_SPACING);
mPoints[3] = new Point(centerX - POINT_SPACING, centerY);
mPoints[4] = new Point(centerX, centerY);
mPoints[5] = new Point(centerX + POINT_SPACING, centerY);
mPoints[6] = new Point(centerX - POINT_SPACING, centerY + POINT_SPACING);
mPoints[7] = new Point(centerX, centerY + POINT_SPACING);
mPoints[8] = new Point(centerX + POINT_SPACING, centerY + POINT_SPACING);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制点
for (int i = 0; i < mPoints.length; i++) {
if (isPointSelected(mPoints[i])) {
canvas.drawCircle(mPoints[i].x, mPoints[i].y, POINT_RADIUS, mSelectedPointPaint);
} else {
canvas.drawCircle(mPoints[i].x, mPoints[i].y, POINT_RADIUS, mPointPaint);
}
}
// 绘制路径
if (!mSelectedPoints.isEmpty()) {
mPath.reset();
mPath.moveTo(mSelectedPoints.get(0).x, mSelectedPoints.get(0).y);
for (int i = 1; i < mSelectedPoints.size(); i++) {
mPath.lineTo(mSelectedPoints.get(i).x, mSelectedPoints.get(i).y);
}
canvas.drawPath(mPath, mLinePaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsDrawing = true;
mSelectedPoints.clear();
mPath.reset();
checkPoint(event.getX(), event.getY());
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
if (mIsDrawing) {
checkPoint(event.getX(), event.getY());
invalidate();
}
return true;
case MotionEvent.ACTION_UP:
mIsDrawing = false;
invalidate();
return true;
}
return super.onTouchEvent(event);
}
private void checkPoint(float x, float y) {
for (Point point : mPoints) {
if (distance(x, y, point.x, point.y) <= POINT_RADIUS && !isPointSelected(point)) {
mSelectedPoints.add(point);
break;
}
}
}
private boolean isPointSelected(Point point) {
return mSelectedPoints.contains(point);
}
private float distance(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
/**
*
* @return
*/
public String getPattern() {
StringBuilder pattern = new StringBuilder();
for (Point point : mSelectedPoints) {
for (int i = 0; i < mPoints.length; i++) {
if (mPoints[i] == point) {
pattern.append(i);
break;
}
}
}
return pattern.toString();
}
}
}

@ -1,314 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
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.tool.DataUtils;
import java.util.HashSet;
public class TrashActivity extends Activity implements OnClickListener {
private static final String TAG = "TrashActivity";
private static final int TRASH_NOTES_QUERY_TOKEN = 0;
private enum ListEditState {
TRASH_NOTE_LIST
}
private ContentResolver mContentResolver;
private BackgroundQueryHandler mBackgroundQueryHandler;
private ListView mNotesListView;
private NotesListAdapter mNotesListAdapter;
private TextView mTitleBar;
private ImageView mBackButton;
private TextView mEmptyText;
private ListEditState mState;
private NoteItemData mFocusNoteDataItem;
private ActionMode mActionMode;
private ModeCallback mModeCallBack;
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.trash_options, menu);
// 为所有菜单项设置点击监听器
menu.findItem(R.id.restore).setOnMenuItemClickListener(this);
menu.findItem(R.id.permanent_delete).setOnMenuItemClickListener(this);
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true);
mNotesListView.setLongClickable(false);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateMenu();
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.restore:
handleRestoreAction();
break;
case R.id.permanent_delete:
// 直接处理永久删除,不依赖任何中间方法
int count = mNotesListAdapter.getCount();
for (int i = 0; i < count; i++) {
if (mNotesListAdapter.isSelectedItem(i)) {
// 获取便签ID
long id = mNotesListAdapter.getItemId(i);
// 直接删除便签及其所有关联数据
getContentResolver().delete(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
null, null);
}
}
// 刷新列表
startAsyncTrashNotesQuery();
break;
}
mActionMode.finish();
return true;
}
private void updateMenu() {
if (mActionMode != null) {
int count = mNotesListAdapter.getSelectedCount();
mActionMode.setTitle(getString(R.string.menu_select_title, count));
}
}
}
private class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case TRASH_NOTES_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
mEmptyText.setVisibility(View.GONE);
mNotesListView.setVisibility(View.VISIBLE);
mNotesListAdapter.changeCursor(cursor);
} else {
mEmptyText.setVisibility(View.VISIBLE);
mNotesListView.setVisibility(View.GONE);
if (cursor != null) {
cursor.close();
}
}
break;
default:
return;
}
}
}
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 回收站中的便签不允许直接点击查看,只能通过长按选择操作
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trash);
mModeCallBack = new ModeCallback();
initResources();
mState = ListEditState.TRASH_NOTE_LIST;
}
@Override
protected void onStart() {
super.onStart();
startAsyncTrashNotesQuery();
}
private void initResources() {
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mTitleBar.setText(getString(R.string.trash_title));
mBackButton = (ImageView) findViewById(R.id.iv_back);
mBackButton.setOnClickListener(this);
mEmptyText = (TextView) findViewById(R.id.tv_empty_trash);
mEmptyText.setText(getString(R.string.trash_empty));
mNotesListView = (ListView) findViewById(R.id.notes_list);
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
mNotesListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mNotesListView.setMultiChoiceModeListener(mModeCallBack);
mNotesListAdapter = new NotesListAdapter(this);
mNotesListView.setAdapter(mNotesListAdapter);
}
private void startAsyncTrashNotesQuery() {
String selection = NoteColumns.PARENT_ID + " = ?";
String[] selectionArgs = new String[] { String.valueOf(Notes.ID_TRASH_FOLDER) };
String sortOrder = NoteColumns.MODIFIED_DATE + " DESC";
mBackgroundQueryHandler.startQuery(TRASH_NOTES_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs, sortOrder);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_back:
finish();
break;
default:
break;
}
}
private void handleRestoreAction() {
HashSet<Long> selectedIds = mNotesListAdapter.getSelectedItemIds();
if (selectedIds != null && selectedIds.size() > 0) {
if (DataUtils.batchRestoreNotes(mContentResolver, selectedIds)) {
Toast.makeText(this, getString(R.string.format_move_notes_to_folder, selectedIds.size(), getString(R.string.menu_restore)), Toast.LENGTH_SHORT).show();
startAsyncTrashNotesQuery();
} else {
Log.e(TAG, "Restore notes error");
}
}
}
private void handlePermanentDeleteAction() {
HashSet<Long> selectedIds = mNotesListAdapter.getSelectedItemIds();
if (selectedIds != null && selectedIds.size() > 0) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setMessage(getString(R.string.alert_message_permanent_delete_notes, selectedIds.size()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
batchPermanentDelete();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
}
private void batchPermanentDelete() {
// 直接从适配器获取选中的便签ID
HashSet<Long> selectedIds = new HashSet<>();
for (int i = 0; i < mNotesListAdapter.getCount(); i++) {
if (mNotesListAdapter.isSelectedItem(i)) {
long id = mNotesListAdapter.getItemId(i);
selectedIds.add(id);
}
}
if (selectedIds.size() > 0) {
Log.d(TAG, "Permanently deleting notes: " + selectedIds);
// 直接使用ContentResolver删除便签
int deletedCount = 0;
for (long id : selectedIds) {
int count = getContentResolver().delete(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
null, null);
if (count > 0) {
deletedCount++;
Log.d(TAG, "Deleted note: " + id);
}
}
Log.d(TAG, "Deleted " + deletedCount + " notes");
// 无论删除结果如何,都刷新列表
startAsyncTrashNotesQuery();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
}

@ -1,42 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import net.micode.notes.tool.DataUtils;
/**
* 广便
*/
public class TrashCleanupReceiver extends BroadcastReceiver {
private static final String TAG = "TrashCleanupReceiver";
private static final int TRASH_RETENTION_DAYS = 30; // 回收站保留天数
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Start cleaning up trash notes");
// 清理超过30天的回收站便签
int deletedCount = DataUtils.cleanupTrashNotes(context.getContentResolver(), TRASH_RETENTION_DAYS);
Log.d(TAG, "Cleanup completed, deleted " + deletedCount + " trash notes");
}
}

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

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

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

@ -24,105 +24,59 @@ import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* MetaData
* Google Task
* Task
*/
public class MetaData extends Task {
// 日志标签
private final static String TAG = MetaData.class.getSimpleName();
// 关联的Google Task ID
private String mRelatedGid = null;
/**
*
* Google Task ID
* @param gid Google Task ID
* @param metaInfo JSON
*/
public void setMeta(String gid, JSONObject metaInfo) {
try {
// 将Google Task ID添加到元信息中
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
// 将元信息JSON字符串设置为笔记内容
setNotes(metaInfo.toString());
// 设置任务名称为元数据笔记的特定名称
setName(GTaskStringUtils.META_NOTE_NAME);
}
/**
* Google Task ID
* @return Google Task ID
*/
public String getRelatedGid() {
return mRelatedGid;
}
/**
*
*
* @return
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
/**
* JSON
* JSONGoogle Task ID
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js); // 调用父类方法设置基础内容
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
// 从笔记内容中解析元信息JSON
JSONObject metaInfo = new JSONObject(getNotes().trim());
// 提取关联的Google Task ID
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null; // 解析失败时设置为null
mRelatedGid = null;
}
}
}
/**
* JSON
* MetaDataJSON
* @param js JSON
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
// 此函数不应被调用因为MetaData不应从本地JSON加载
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
/**
* JSON
* MetaDataJSON
* @return JSON
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
/**
*
* MetaData
* @param c
* @return
*/
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}
}

@ -20,149 +20,82 @@ import android.database.Cursor;
import org.json.JSONObject;
/**
* Node
*
*
*/
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; // 同步错误
// Google Task ID
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
private String mGid;
// 节点名称
private String mName;
// 最后修改时间戳
private long mLastModified;
// 是否已删除
private boolean mDeleted;
/**
*
*
*/
public Node() {
mGid = null; // Google Task ID初始为null
mName = ""; // 名称初始为空字符串
mLastModified = 0; // 最后修改时间初始为0
mDeleted = false; // 删除标志初始为false
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
/**
* JSON
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getCreateAction(int actionId);
/**
* JSON
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getUpdateAction(int actionId);
/**
* JSON
*
* @param js JSON
*/
public abstract void setContentByRemoteJSON(JSONObject js);
/**
* JSON
*
* @param js JSON
*/
public abstract void setContentByLocalJSON(JSONObject js);
/**
* JSON
*
* @return JSON
*/
public abstract JSONObject getLocalJSONFromContent();
/**
*
*
* @param c
* @return
*/
public abstract int getSyncAction(Cursor c);
/**
* Google Task ID
* @param gid Google Task ID
*/
public void setGid(String gid) {
this.mGid = gid;
}
/**
*
* @param name
*/
public void setName(String name) {
this.mName = name;
}
/**
*
* @param lastModified
*/
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
/**
*
* @param deleted
*/
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
/**
* Google Task ID
* @return Google Task ID
*/
public String getGid() {
return this.mGid;
}
/**
*
* @return
*/
public String getName() {
return this.mName;
}
/**
*
* @return
*/
public long getLastModified() {
return this.mLastModified;
}
/**
*
* @return
*/
public boolean getDeleted() {
return this.mDeleted;
}
}
}

@ -34,202 +34,140 @@ import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
/**
* SqlData
* SQLiteJSON
*
*/
public class SqlData {
// 日志标签
private static final String TAG = SqlData.class.getSimpleName();
// 无效ID常量
private static final int INVALID_ID = -99999;
/**
*
*
*/
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, // 数据ID
DataColumns.MIME_TYPE, // MIME类型
DataColumns.CONTENT, // 内容
DataColumns.DATA1, // 数据列1
DataColumns.DATA3 // 数据列3
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
// 数据ID列索引
public static final int DATA_ID_COLUMN = 0;
// MIME类型列索引
public static final int DATA_MIME_TYPE_COLUMN = 1;
// 内容列索引
public static final int DATA_CONTENT_COLUMN = 2;
// 数据列1索引
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
// 数据列3索引
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
// 内容解析器用于访问ContentProvider
private ContentResolver mContentResolver;
// 是否为新建数据记录
private boolean mIsCreate;
// 数据ID
private long mDataId;
// MIME类型
private String mDataMimeType;
// 内容
private String mDataContent;
// 数据列1的值
private long mDataContentData1;
// 数据列3的值
private String mDataContentData3;
// 存储有变动的数据值的ContentValues对象
private ContentValues mDiffDataValues;
/**
* -
* @param context
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true; // 标记为新记录
mDataId = INVALID_ID; // 初始化为无效ID
mDataMimeType = DataConstants.NOTE; // 默认MIME类型为笔记
mDataContent = ""; // 内容初始化为空字符串
mDataContentData1 = 0; // 数据列1初始化为0
mDataContentData3 = ""; // 数据列3初始化为空字符串
mDiffDataValues = new ContentValues(); // 初始化差异值容器
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
/**
* -
* @param context
* @param c
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为现有记录
loadFromCursor(c); // 从游标加载数据
mDiffDataValues = new ContentValues(); // 初始化差异值容器
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
/**
*
* @param c
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN); // 加载数据ID
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); // 加载MIME类型
mDataContent = c.getString(DATA_CONTENT_COLUMN); // 加载内容
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); // 加载数据列1
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 加载数据列3
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/**
* JSON
* JSON
* @param js JSON
* @throws JSONException JSON
*/
public void setContent(JSONObject js) throws JSONException {
// 处理数据ID
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId); // 记录变化
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId; // 更新当前值
mDataId = dataId;
// 处理MIME类型
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 记录变化
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType; // 更新当前值
mDataMimeType = dataMimeType;
// 处理内容
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent); // 记录变化
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent; // 更新当前值
mDataContent = dataContent;
// 处理数据列1
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 记录变化
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1; // 更新当前值
mDataContentData1 = dataContentData1;
// 处理数据列3
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 记录变化
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3; // 更新当前值
mDataContentData3 = dataContentData3;
}
/**
* JSON
* @return JSON
* @throws JSONException JSON
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId); // 添加数据ID
js.put(DataColumns.MIME_TYPE, mDataMimeType); // 添加MIME类型
js.put(DataColumns.CONTENT, mDataContent); // 添加内容
js.put(DataColumns.DATA1, mDataContentData1); // 添加数据列1
js.put(DataColumns.DATA3, mDataContentData3); // 添加数据列3
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
/**
*
*
* @param noteId ID
* @param validateVersion
* @param version
*/
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
// 新记录:插入操作
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID); // 移除无效ID
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 添加笔记ID
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); // 插入数据
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,
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
@ -241,16 +179,11 @@ public class SqlData {
}
}
// 重置状态
mDiffDataValues.clear(); // 清空差异值
mIsCreate = false; // 标记为现有记录
mDiffDataValues.clear();
mIsCreate = false;
}
/**
* ID
* @return ID
*/
public long getId() {
return mDataId;
}
}
}

@ -37,394 +37,318 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* SqlNote
* SQLiteJSON
*
*/
public class SqlNote {
// 日志标签
private static final String TAG = SqlNote.class.getSimpleName();
// 无效ID常量
private static final int INVALID_ID = -99999;
/**
*
*
*/
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, // 笔记ID
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.CREATED_DATE, // 创建日期
NoteColumns.HAS_ATTACHMENT, // 是否有附件
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.NOTES_COUNT, // 笔记数量(文件夹用)
NoteColumns.PARENT_ID, // 父节点ID
NoteColumns.SNIPPET, // 内容摘要
NoteColumns.TYPE, // 类型(笔记/文件夹/系统)
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.SYNC_ID, // 同步ID
NoteColumns.LOCAL_MODIFIED, // 本地修改标志
NoteColumns.ORIGIN_PARENT_ID, // 原始父节点ID
NoteColumns.GTASK_ID, // Google Task ID
NoteColumns.VERSION // 版本号
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
};
// 笔记ID列索引
public static final int ID_COLUMN = 0;
// 提醒日期列索引
public static final int ALERTED_DATE_COLUMN = 1;
// 背景颜色ID列索引
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;
// 父节点ID列索引
public static final int PARENT_ID_COLUMN = 7;
// 内容摘要列索引
public static final int SNIPPET_COLUMN = 8;
// 类型列索引
public static final int TYPE_COLUMN = 9;
// 小部件ID列索引
public static final int WIDGET_ID_COLUMN = 10;
// 小部件类型列索引
public static final int WIDGET_TYPE_COLUMN = 11;
// 同步ID列索引
public static final int SYNC_ID_COLUMN = 12;
// 本地修改标志列索引
public static final int LOCAL_MODIFIED_COLUMN = 13;
// 原始父节点ID列索引
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
// Google Task ID列索引
public static final int GTASK_ID_COLUMN = 15;
// 版本号列索引
public static final int VERSION_COLUMN = 16;
// 上下文对象
private Context mContext;
// 内容解析器
private ContentResolver mContentResolver;
// 是否为新建笔记
private boolean mIsCreate;
// 笔记ID
private long mId;
// 提醒日期
private long mAlertDate;
// 背景颜色ID
private int mBgColorId;
// 创建日期
private long mCreatedDate;
// 是否有附件0:无, 1:有)
private int mHasAttachment;
// 修改日期
private long mModifiedDate;
// 父节点ID
private long mParentId;
// 内容摘要
private String mSnippet;
// 笔记类型
private int mType;
// 小部件ID
private int mWidgetId;
// 小部件类型
private int mWidgetType;
// 原始父节点ID用于恢复
private long mOriginParent;
// 版本号
private long mVersion;
// 存储有变动的笔记值的ContentValues对象
private ContentValues mDiffNoteValues;
// 关联的数据列表
private ArrayList<SqlData> mDataList;
/**
* -
* @param context
*/
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true; // 标记为新笔记
mId = INVALID_ID; // 初始化为无效ID
mAlertDate = 0; // 默认无提醒
mBgColorId = ResourceParser.getDefaultBgId(context); // 默认背景颜色
mCreatedDate = System.currentTimeMillis(); // 创建时间为当前时间
mHasAttachment = 0; // 默认无附件
mModifiedDate = System.currentTimeMillis(); // 修改时间为当前时间
mParentId = 0; // 默认父节点为根节点
mSnippet = ""; // 内容摘要为空
mType = Notes.TYPE_NOTE; // 默认为笔记类型
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // 默认无效小部件ID
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 默认无效小部件类型
mOriginParent = 0; // 默认无原始父节点
mVersion = 0; // 初始版本号为0
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
mIsCreate = true;
mId = INVALID_ID;
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);
mCreatedDate = System.currentTimeMillis();
mHasAttachment = 0;
mModifiedDate = System.currentTimeMillis();
mParentId = 0;
mSnippet = "";
mType = Notes.TYPE_NOTE;
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mOriginParent = 0;
mVersion = 0;
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList<SqlData>();
}
/**
* -
* @param context
* @param c
*/
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为现有笔记
loadFromCursor(c); // 从游标加载笔记信息
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
mIsCreate = false;
loadFromCursor(c);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 如果是笔记类型,加载关联的数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
loadDataContent();
mDiffNoteValues = new ContentValues();
}
/**
* - ID
* @param context
* @param id ID
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为现有笔记
loadFromCursor(id); // 根据ID加载笔记信息
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
mIsCreate = false;
loadFromCursor(id);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 如果是笔记类型,加载关联的数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
loadDataContent();
mDiffNoteValues = new ContentValues();
}
/**
* ID
* @param id ID
*/
private void loadFromCursor(long id) {
Cursor c = null;
try {
// 查询指定ID的笔记
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext(); // 移动到第一条记录
loadFromCursor(c); // 从游标加载数据
c.moveToNext();
loadFromCursor(c);
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally {
if (c != null)
c.close(); // 确保关闭游标
c.close();
}
}
/**
*
* @param c
*/
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); // 加载版本号
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
mParentId = c.getLong(PARENT_ID_COLUMN);
mSnippet = c.getString(SNIPPET_COLUMN);
mType = c.getInt(TYPE_COLUMN);
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
/**
*
*
*/
private void loadDataContent() {
Cursor c = null;
mDataList.clear(); // 清空现有数据列表
mDataList.clear();
try {
// 查询关联的数据记录
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
// 遍历所有数据记录
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c); // 创建SqlData对象
mDataList.add(data); // 添加到数据列表
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close(); // 确保关闭游标
c.close();
}
}
/**
* JSON
* JSON
* @param js JSON
* @return
*/
public boolean setContent(JSONObject js) {
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记信息
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder"); // 不能设置系统文件夹
Log.w(TAG, "cannot set system folder");
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// 处理文件夹:只能更新摘要和类型
// for folder we can only update the snnipet and type
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); // 记录变化
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type; // 更新当前值
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
// 处理笔记:更新所有字段
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
// 处理笔记ID
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); // 记录变化
mDiffNoteValues.put(NoteColumns.ID, id);
}
mId = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
mAlertDate = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
mBgColorId = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
}
mCreatedDate = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
}
mHasAttachment = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
}
mModifiedDate = modifiedDate; // 更新当前值
mModifiedDate = modifiedDate;
// 处理父节点ID
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); // 记录变化
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
mParentId = parentId; // 更新当前值
mParentId = parentId;
// 处理内容摘要
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); // 记录变化
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
}
mWidgetId = 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); // 记录变化
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
mWidgetType = widgetType; // 更新当前值
mWidgetType = widgetType;
// 处理原始父节点ID
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); // 记录变化
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent; // 更新当前值
mOriginParent = originParent;
// 处理关联的数据
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i); // 获取数据对象
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
// 根据数据ID查找现有的SqlData对象
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp; // 找到现有对象
break;
sqlData = temp;
}
}
}
// 如果没找到创建新的SqlData对象
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData); // 添加到数据列表
mDataList.add(sqlData);
}
sqlData.setContent(data); // 设置数据内容
sqlData.setContent(data);
}
}
} catch (JSONException e) {
@ -435,10 +359,6 @@ public class SqlNote {
return true;
}
/**
* JSON
* @return JSON
*/
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
@ -450,7 +370,6 @@ public class SqlNote {
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);
@ -463,23 +382,21 @@ public class SqlNote {
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); // 添加笔记信息
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
// 添加关联数据
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data); // 添加到数据数组
dataArray.put(data);
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 添加数据信息
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
// 处理文件夹或系统类型:只包含必要字段
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 添加笔记信息
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
}
return js;
@ -490,86 +407,47 @@ public class SqlNote {
return null;
}
/**
* ID
* @param id ID
*/
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 记录变化
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
/**
* Google Task ID
* @param gid Google Task ID
*/
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 记录变化
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
/**
* ID
* @param syncId ID
*/
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 记录变化
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
/**
*
* 0
*/
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 记录变化
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
/**
* ID
* @return ID
*/
public long getId() {
return mId;
}
/**
* ID
* @return ID
*/
public long getParentId() {
return mParentId;
}
/**
*
* @return
*/
public String getSnippet() {
return mSnippet;
}
/**
*
* @return
*/
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
/**
*
*
* @param validateVersion
*/
public void commit(boolean validateVersion) {
if (mIsCreate) {
// 新笔记:插入操作
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID); // 移除无效ID
mDiffNoteValues.remove(NoteColumns.ID);
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); // 插入笔记
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
// 从返回的URI中提取新笔记的ID
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
@ -579,33 +457,27 @@ public class SqlNote {
throw new IllegalStateException("Create thread id failed");
}
// 如果是笔记类型,提交关联的数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1); // 提交数据,不验证版本
sqlData.commit(mId, false, -1);
}
}
} else {
// 现有笔记:更新操作
// 验证笔记ID系统文件夹除外
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
if (mDiffNoteValues.size() > 0) {
mVersion ++; // 增加版本号
mVersion ++;
int result = 0;
if (!validateVersion) {
// 不验证版本号:直接更新
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
String.valueOf(mId)
});
} else {
// 验证版本号:确保版本匹配
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
@ -615,21 +487,19 @@ public class SqlNote {
}
}
// 如果是笔记类型,提交关联的数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion); // 提交数据,验证版本
sqlData.commit(mId, validateVersion, mVersion);
}
}
}
// 刷新本地信息
loadFromCursor(mId); // 重新从数据库加载笔记信息
// refresh local info
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 重新加载关联数据
loadDataContent();
// 重置状态
mDiffNoteValues.clear(); // 清空差异值
mIsCreate = false; // 标记为现有笔记
mDiffNoteValues.clear();
mIsCreate = false;
}
}
}

@ -31,81 +31,65 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Task
* Google Task
* Node
*/
public class Task extends Node {
// 日志标签
private static final String TAG = Task.class.getSimpleName();
// 任务是否已完成
private boolean mCompleted;
// 任务备注/笔记内容
private String mNotes;
// 元信息JSON对象包含本地数据库信息
private JSONObject mMetaInfo;
// 前一个兄弟任务(用于保持任务顺序)
private Task mPriorSibling;
// 父任务列表
private TaskList mParent;
/**
*
*
*/
public Task() {
super();
mCompleted = false; // 默认未完成
mNotes = null; // 默认无备注
mPriorSibling = null; // 默认无前兄弟
mParent = null; // 默认无父列表
mMetaInfo = null; // 默认无元信息
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:创建
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 动作ID
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 索引位置(在父列表中的位置)
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// 实体增量(包含任务信息)
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 任务名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 实体类型:任务
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 任务备注
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// 父节点ID
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// 目标父节点类型
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 列表ID
// list_id
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// 前兄弟节点ID用于确定插入位置
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
@ -119,33 +103,27 @@ public class Task extends Node {
return js;
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:更新
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 动作ID
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 任务ID
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 实体增量(包含更新的任务信息)
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 任务名称
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 任务备注
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 删除标志
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
@ -157,40 +135,35 @@ public class Task extends Node {
return js;
}
/**
* JSON
* Google TasksJSON
* @param js JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// 任务ID
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 最后修改时间
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 任务名称
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// 任务备注
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// 删除标志
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// 完成状态
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
@ -202,11 +175,6 @@ public class Task extends Node {
}
}
/**
* JSON
* JSON
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
@ -214,21 +182,17 @@ public class Task extends Node {
}
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;
}
@ -240,48 +204,41 @@ public class Task extends Node {
}
}
/**
* JSON
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
// 从Web创建的新任务无元信息
// new task created from web
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
// 创建新的JSON结构
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name); // 笔记内容
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 数据部分
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 笔记类型
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 笔记信息
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
// 已同步的任务(有元信息)
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 更新笔记内容
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName()); // 更新笔记内容
data.put(DataColumns.CONTENT, getName());
break;
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 更新笔记类型
return mMetaInfo; // 返回更新后的元信息
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
@ -290,75 +247,59 @@ public class Task extends Node {
}
}
/**
*
* MetaData
* @param metaData MetaData
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
// 从备注中解析元信息JSON
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null; // 解析失败时设置为null
mMetaInfo = null;
}
}
}
/**
*
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
// 从元信息中获取笔记信息
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE; // 元信息被删除,需要更新远程
return SYNC_ACTION_UPDATE_REMOTE;
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL; // 远程笔记ID被删除需要更新本地
return SYNC_ACTION_UPDATE_LOCAL;
}
// 验证笔记ID是否匹配
// validate the note id now
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL; // ID不匹配需要更新本地
return SYNC_ACTION_UPDATE_LOCAL;
}
// 检查本地修改标志
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 无本地更新
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 两端均无更新
// no update both side
return SYNC_ACTION_NONE;
} else {
// 将远程更新应用到本地
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// 有本地更新
// 验证Google Task ID是否匹配
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR; // ID不匹配返回错误
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 只有本地有修改
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// 两端都有修改,发生冲突
return SYNC_ACTION_UPDATE_CONFLICT;
}
}
@ -367,21 +308,14 @@ public class Task extends Node {
e.printStackTrace();
}
return SYNC_ACTION_ERROR; // 发生异常,返回错误
return SYNC_ACTION_ERROR;
}
/**
*
*
* @return
*/
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
// 以下为属性的getter和setter方法
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
@ -413,4 +347,5 @@ public class Task extends Node {
public TaskList getParent() {
return this.mParent;
}
}
}

@ -29,56 +29,40 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* TaskList
* Google Task
* NodeTask
*/
public class TaskList extends Node {
// 日志标签
private static final String TAG = TaskList.class.getSimpleName();
// 索引位置(在父节点中的位置)
private int mIndex;
// 子任务列表
private ArrayList<Task> mChildren;
/**
*
*
*/
public TaskList() {
super();
mChildren = new ArrayList<Task>(); // 初始化子任务列表
mIndex = 1; // 默认索引为1
mChildren = new ArrayList<Task>();
mIndex = 1;
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:创建
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 动作ID
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 索引位置
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// 实体增量(包含任务列表信息)
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 列表名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 实体类型:组/列表
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
@ -90,30 +74,24 @@ public class TaskList extends Node {
return js;
}
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 动作类型:更新
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 动作ID
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 列表ID
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 实体增量(包含更新的列表信息)
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 列表名称
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 删除标志
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
@ -125,25 +103,20 @@ public class TaskList extends Node {
return js;
}
/**
* JSON
* Google TasksJSON
* @param js JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// 列表ID
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 最后修改时间
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 列表名称
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
@ -156,36 +129,27 @@ public class TaskList extends Node {
}
}
/**
* JSON
* JSON
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
// 解析文件夹信息
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 根据文件夹类型设置名称
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// 普通文件夹
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); // 添加MIUI前缀
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); // 默认文件夹
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); // 通话记录文件夹
+ GTaskStringUtils.FOLDER_CALL_NOTE);
else
Log.e(TAG, "invalid system folder"); // 无效的系统文件夹
Log.e(TAG, "invalid system folder");
} else {
Log.e(TAG, "error type"); // 错误类型
Log.e(TAG, "error type");
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
@ -193,32 +157,23 @@ public class TaskList extends Node {
}
}
/**
* JSON
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
// 处理文件夹名称移除MIUI前缀
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); // 文件夹名称
// 设置文件夹类型
folder.put(NoteColumns.SNIPPET, folderName);
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 系统文件夹
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 普通文件夹
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
js.put(GTaskStringUtils.META_HEAD_NOTE, folder); // 添加到JSON
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js;
} catch (JSONException e) {
@ -228,36 +183,28 @@ public class TaskList extends Node {
}
}
/**
*
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
// 检查本地修改标志
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 无本地更新
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 两端均无更新
// no update both side
return SYNC_ACTION_NONE;
} else {
// 将远程更新应用到本地
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// 有本地更新
// 验证Google Task ID是否匹配
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR; // ID不匹配返回错误
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;
}
}
@ -266,42 +213,27 @@ public class TaskList extends Node {
e.printStackTrace();
}
return SYNC_ACTION_ERROR; // 发生异常,返回错误
return SYNC_ACTION_ERROR;
}
/**
*
* @return
*/
public int getChildTaskCount() {
return mChildren.size();
}
/**
*
* @param task
* @return
*/
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task); // 添加到列表末尾
ret = mChildren.add(task);
if (ret) {
// 设置前兄弟节点和父节点
// need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1)); // 前兄弟为前一个元素(如果存在)
task.setParent(this); // 设置父节点为当前列表
.get(mChildren.size() - 1));
task.setParent(this);
}
}
return ret;
}
/**
*
* @param task
* @param index
* @return
*/
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
@ -310,43 +242,37 @@ public class TaskList extends Node {
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task); // 插入到指定位置
mChildren.add(index, task);
// 更新任务列表的前兄弟关系
// update the task list
Task preTask = null;
Task afterTask = null;
if (index != 0)
preTask = mChildren.get(index - 1); // 前一个任务
preTask = mChildren.get(index - 1);
if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1); // 后一个任务
afterTask = mChildren.get(index + 1);
task.setPriorSibling(preTask); // 设置前兄弟节点
task.setPriorSibling(preTask);
if (afterTask != null)
afterTask.setPriorSibling(task); // 更新后一个任务的前兄弟节点
afterTask.setPriorSibling(task);
}
return true;
}
/**
*
* @param task
* @return
*/
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
if (index != -1) {
ret = mChildren.remove(task); // 移除任务
ret = mChildren.remove(task);
if (ret) {
// 重置任务的前兄弟节点和父节点
// reset prior sibling and parent
task.setPriorSibling(null);
task.setParent(null);
// 更新任务列表
// update the task list
if (index != mChildren.size()) {
// 更新被移除任务后一个任务的前兄弟节点
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
}
@ -355,13 +281,8 @@ public class TaskList extends Node {
return ret;
}
/**
*
* @param task
* @param index
* @return
*/
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
@ -373,18 +294,11 @@ public class TaskList extends Node {
return false;
}
if (pos == index) // 位置未变
if (pos == index)
return true;
// 先移除再添加到新位置
return (removeChildTask(task) && addChildTask(task, index));
}
/**
* Google Task ID
* @param gid Google Task ID
* @return null
*/
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
@ -395,20 +309,10 @@ public class TaskList extends Node {
return null;
}
/**
*
* @param task
* @return -1
*/
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
/**
*
* @param index
* @return null
*/
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
@ -417,11 +321,6 @@ public class TaskList extends Node {
return mChildren.get(index);
}
/**
* Google Task ID
* @param gid Google Task ID
* @return null
*/
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))
@ -430,16 +329,10 @@ public class TaskList extends Node {
return null;
}
/**
*
* @return
*/
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
}
// 以下为属性的getter和setter方法
public void setIndex(int index) {
this.mIndex = index;
}
@ -447,4 +340,4 @@ public class TaskList extends Node {
public int getIndex() {
return this.mIndex;
}
}
}

@ -63,46 +63,24 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
});
}
// private void showNotification(int tickerId, String content) {
// Notification notification = new Notification(R.drawable.notification, mContext
// .getString(tickerId), System.currentTimeMillis());
// notification.defaults = Notification.DEFAULT_LIGHTS;
// notification.flags = Notification.FLAG_AUTO_CANCEL;
// PendingIntent pendingIntent;
// if (tickerId != R.string.ticker_success) {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesPreferenceActivity.class), 0);
//
// } else {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesListActivity.class), 0);
// }
// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
// pendingIntent);
// mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// }
private void showNotification(int tickerId, String content) {
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
private void showNotification(int tickerId, String content) {
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
}
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent);
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
Notification.Builder builder = new Notification.Builder(mContext)
.setAutoCancel(true)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(content)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true);
Notification notification=builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
@Override
protected Integer doInBackground(Void... unused) {

@ -263,7 +263,7 @@ public class GTaskManager {
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_FOLDER)
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
@ -293,7 +293,7 @@ public class GTaskManager {
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_FOLDER)
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
@ -428,7 +428,7 @@ public class GTaskManager {
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_FOLDER)
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
@ -761,7 +761,7 @@ public class GTaskManager {
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_FOLDER)
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {

@ -234,7 +234,7 @@ public class BackupUtils {
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER + ") OR "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {

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

Loading…
Cancel
Save