zhangqing 3 weeks ago
parent a47e5924c3
commit ff3327e1e4

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

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

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

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

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

@ -26,6 +26,7 @@
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- Title Bar -->
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="fill_parent"
@ -37,6 +38,61 @@
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"
@ -45,9 +101,36 @@
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@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>
</LinearLayout>
<!-- New Note Button -->
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"

@ -49,4 +49,16 @@
<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,4 +36,8 @@
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"/>
<item
android:id="@+id/menu_trash"
android:title="@string/menu_trash"/>
</menu>

@ -17,6 +17,16 @@
<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,4 +21,7 @@
<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>

@ -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,6 +119,13 @@
<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,6 +120,13 @@
<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,6 +48,8 @@
<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>
@ -61,7 +63,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">Change folder name</string>
<string name="menu_folder_change_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>
@ -126,10 +128,18 @@
<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,10 +60,21 @@
<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:displayOptions" />
<item name="android:visibility">gone</item>
<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>
</style>
</resources>

@ -14,88 +14,97 @@
* limitations under the License.
*/
package net.micode.notes.data; // 包声明
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 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; // 导入HashMap类用于缓存联系人信息
import java.util.HashMap;
/**
*
*
*
*/
public class Contact {
// 联系人缓存使用HashMap存储电话号码到联系人姓名的映射提高查询效率
/**
*
*
*/
private static HashMap<String, String> sContactCache;
// 日志标签
/**
*
*/
private static final String TAG = "Contact";
/**
* ID
*
* 使PHONE_NUMBERS_EQUAL
*/
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER // 电话号码相等比较
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" // MIME类型为电话类型
+ " AND " + Data.RAW_CONTACT_ID + " IN " // 原始联系人ID在子查询中
+ "(SELECT raw_contact_id " // 子查询开始
+ " FROM phone_lookup" // 从电话查找表
+ " WHERE min_match = '+')"; // 最小匹配条件
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
/**
*
*
* @param context 访
*
* 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>(); // 创建新的HashMap
sContactCache = new HashMap<String, String>();
}
// 先从缓存中查找,如果找到则直接返回
// 先从缓存中查找,提高查询效率
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber); // 返回缓存中的姓名
}
// 构建查询条件:替换选择语句中的'+'为最小匹配数
// PhoneNumberUtils.toCallerIDMinMatch将电话号码转换为最小匹配格式
String selection = CALLER_ID_SELECTION.replace("+", // 替换占位符
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); // 获取电话号码的最小匹配格式
// 使用PhoneNumberUtils.toCallerIDMinMatch确保电话号码格式正确匹配
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 查询联系人数据库
// 使用ContentResolver查询系统联系人数据
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI, // 查询URI联系人数据URI
new String [] { Phone.DISPLAY_NAME }, // 要返回的列:显示名称
selection, // 选择条件
new String[] { phoneNumber }, // 选择参数:电话号码
null); // 排序方式(无)
Data.CONTENT_URI, // 查询URI
new String [] { Phone.DISPLAY_NAME }, // 要返回的列:显示名称
selection, // 选择条件
new String[] { phoneNumber }, // 选择参数:电话号码
null); // 排序方式(无)
// 处理查询结果
if (cursor != null && cursor.moveToFirst()) { // 如果游标不为空且有数据
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0); // 获取第一列的显示名称索引0
sContactCache.put(phoneNumber, 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
} finally {
cursor.close(); // 确保关闭游标,释放资源
cursor.close(); // 确保关闭游标,防止资源泄漏
}
} else {
// 没有找到匹配的联系人
Log.d(TAG, "No contact matched with number:" + phoneNumber); // 记录调试日志
return null; // 返回null
return null; // 没有匹配的联系人,返回null
}
}
}

@ -16,292 +16,363 @@
package net.micode.notes.data;
import android.net.Uri; // 导入Android URI类用于定义内容提供者的URI
import android.net.Uri;
/**
*
* URI
*
*/
public class Notes {
// 内容提供者的授权标识用于ContentProvider的authority属性
// 内容提供者的授权标识用于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}
*/
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_FOLER = -3; // 回收站文件夹ID
// Intent额外数据键名常量用于在不同组件间传递数据
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景颜色ID
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 小部件ID
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 小部件类型
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期
// 小部件类型常量
public static final int TYPE_WIDGET_INVALIDE = -1; // 无效小部件类型
public static final int TYPE_WIDGET_2X = 0; // 2x大小小部件
public static final int TYPE_WIDGET_4X = 1; // 4x大小小部件
public static 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大小小部件
/**
*
* MIME
* 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 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
* URI访
* ContentResolver
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* URI
* 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字段名
public static final String ID = "_id";
/**
* ID
* <P> : INTEGER (long) </P>
* parent_id0
*/
public static final String PARENT_ID = "parent_id"; // 父文件夹ID字段名
public static final String PARENT_ID = "parent_id";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String CREATED_DATE = "created_date"; // 创建日期字段名
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String MODIFIED_DATE = "modified_date"; // 修改日期字段名
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String ALERTED_DATE = "alert_date"; // 提醒日期字段名
public static final String ALERTED_DATE = "alert_date";
/**
*
* <P> : TEXT </P>
*
*/
public static final String SNIPPET = "snippet"; // 摘要字段名
public static final String SNIPPET = "snippet";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String WIDGET_ID = "widget_id"; // 小部件ID字段名
public static final String WIDGET_ID = "widget_id";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String WIDGET_TYPE = "widget_type"; // 小部件类型字段名
public static final String WIDGET_TYPE = "widget_type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String BG_COLOR_ID = "bg_color_id"; // 背景颜色ID字段名
public static final String BG_COLOR_ID = "bg_color_id";
/**
*
* <P> : INTEGER </P>
* 01
*/
public static final String HAS_ATTACHMENT = "has_attachment"; // 是否有附件字段名
public static final String HAS_ATTACHMENT = "has_attachment";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String NOTES_COUNT = "notes_count"; // 笔记数量字段名
public static final String NOTES_COUNT = "notes_count";
/**
*
* <P> : INTEGER </P>
* TYPE_NOTETYPE_FOLDERTYPE_SYSTEM
*/
public static final String TYPE = "type"; // 类型字段名
public static final String TYPE = "type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String SYNC_ID = "sync_id"; // 同步ID字段名
public static final String SYNC_ID = "sync_id";
/**
*
* <P> : INTEGER </P>
* 01
*/
public static final String LOCAL_MODIFIED = "local_modified"; // 本地修改标记字段名
public static final String LOCAL_MODIFIED = "local_modified";
/**
* ID
* <P> : INTEGER </P>
*
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id"; // 原始父文件夹ID字段名
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* GoogleID
* <P> : TEXT </P>
* Google Tasks
*/
public static final String GTASK_ID = "gtask_id"; // Google任务ID字段名
public static final String GTASK_ID = "gtask_id";
/**
*
* <P> : INTEGER (long) </P>
*
*/
public static final String VERSION = "version"; // 版本号字段名
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"; // 主键ID字段名
public static final String ID = "_id";
/**
* MIME
* <P> : Text </P>
*
*/
public static final String MIME_TYPE = "mime_type"; // MIME类型字段名
public static final String MIME_TYPE = "mime_type";
/**
* ID
* <P> : INTEGER (long) </P>
* ID
*/
public static final String NOTE_ID = "note_id"; // 笔记ID字段名
public static final String NOTE_ID = "note_id";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date"; // 创建日期字段名
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date"; // 修改日期字段名
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> : TEXT </P>
*
*/
public static final String CONTENT = "content"; // 内容字段名
public static final String CONTENT = "content";
/**
* {@link #MIMETYPE}
* <P> : INTEGER </P>
* /
*/
public static final String DATA1 = "data1"; // 通用数据字段1
public static final String DATA1 = "data1";
/**
* {@link #MIMETYPE}
* <P> : INTEGER </P>
*/
public static final String DATA2 = "data2"; // 通用数据字段2
public static final String DATA2 = "data2";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
*
*/
public static final String DATA3 = "data3"; // 通用数据字段3
public static final String DATA3 = "data3";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
*/
public static final String DATA4 = "data4"; // 通用数据字段4
public static final String DATA4 = "data4";
/**
* {@link #MIMETYPE}
* <P> : TEXT </P>
*/
public static final String DATA5 = "data5"; // 通用数据字段5
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; // 模式字段使用DATA1列
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"; // 单项目类型
// 内容类型常量用于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
// 文本笔记的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; // 通话日期字段使用DATA1列
public static final String CALL_DATE = DATA1;
/**
*
* <P> : TEXT </P>
* DATA3
*/
public static final String PHONE_NUMBER = DATA3; // 电话号码字段使用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"; // 单项目类型
// 内容类型常量用于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
// 通话笔记的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");
}
}

@ -33,10 +33,10 @@ import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称常量
private static final String DB_NAME = "note.db";
// 数据库版本常量,用于数据库升级
private static final int DB_VERSION = 4;
private static final int DB_VERSION = 5;
/**
*
*
@ -44,203 +44,204 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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" + // 版本号
")";
"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文本
")";
"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字段上创建索引
"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"; // 触发器结束
"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"; // 触发器结束
"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"; // 触发器结束
"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"; // 触发器结束
"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"; // 触发器结束
"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"; // 触发器结束
"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"; // 触发器结束
"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"; // 触发器结束
"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"; // 触发器结束
"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_FOLER + // 仅当新父文件夹是回收站时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + // 设置父文件夹为回收站
" 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_FOLDER + // 仅当新父文件夹是回收站时
" BEGIN" + // 触发器开始
" UPDATE " + TABLE.NOTE + // 更新笔记表
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLDER + // 设置父文件夹为回收站
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 更新原文件夹下的所有笔记
" END"; // 触发器结束
/**
*
* @param context
@ -248,7 +249,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION); // 调用父类构造函数
}
/**
*
* @param db SQLite
@ -259,7 +260,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created"); // 日志记录
}
/**
*
* @param db SQLite
@ -273,7 +274,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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);
@ -283,14 +284,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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(); // 创建内容值对象
/**
*
*
@ -298,7 +299,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); // 设置ID为通话记录文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
/**
*
*
@ -307,7 +308,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); // 设置ID为根文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
/**
*
*
@ -316,17 +317,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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_FOLER); // 设置ID为回收站文件夹ID
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
@ -337,7 +338,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建数据表索引
Log.d(TAG, "data table has been created"); // 日志记录
}
/**
*
* @param db SQLite
@ -347,13 +348,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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);
}
/**
*
* 使
@ -366,7 +367,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
}
return mInstance; // 返回实例
}
/**
*
*
@ -377,7 +378,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据表
}
/**
*
*
@ -389,40 +390,46 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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
*
@ -434,7 +441,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createNoteTable(db); // 重新创建note表
createDataTable(db); // 重新创建data表
}
/**
* V3
* GoogleID
@ -445,18 +452,18 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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_FOLER); // 设置ID为回收站文件夹ID
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLDER); // 设置ID为回收站文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入数据
}
/**
* V4
*
@ -466,4 +473,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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"); // 添加置顶状态字段
}
}

@ -14,58 +14,59 @@
* limitations under the License.
*/
package net.micode.notes.data; // 包声明
package net.micode.notes.data;
import android.app.SearchManager; // 导入搜索管理器类
import android.content.ContentProvider; // 导入内容提供者基类
import android.content.ContentUris; // 导入内容URI工具类
import android.content.ContentValues; // 导入内容值类
import android.content.Intent; // 导入意图类
import android.content.UriMatcher; // 导入URI匹配器类
import android.database.Cursor; // 导入数据库游标类
import android.database.sqlite.SQLiteDatabase; // 导入SQLite数据库类
import android.net.Uri; // 导入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; // 导入表名接口
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;
/**
*
* ContentProviderCRUD
*
* CRUD
* 访
*/
public class NotesProvider extends ContentProvider {
// URI匹配器用于匹配不同的URI请求
// URI匹配器用于匹配不同的URI请求将不同的URI映射到对应的操作
private static final UriMatcher mMatcher;
// 数据库帮助类实例
// 数据库帮助类实例,负责数据库的创建和版本管理
private NotesDatabaseHelper mHelper;
// 日志标签
private static final String TAG = "NotesProvider";
// URI匹配码常量
private static final int URI_NOTE = 1; // 笔记表
private static final int URI_NOTE_ITEM = 2; // 单个笔记项
private static final int URI_DATA = 3; // 数据表
private static final int URI_DATA_ITEM = 4; // 单个数据项
private static final int URI_SEARCH = 5; // 搜索
private static final int URI_SEARCH_SUGGEST = 6; // 搜索建议
// 静态代码块初始化URI匹配器
// 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匹配器
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); // 匹配单个数据
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 匹配搜索
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.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 匹配带查询词的搜索建议
}
/**
@ -74,255 +75,276 @@ public class NotesProvider extends ContentProvider {
* '\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 + "," // 建议图标使用资源ID
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," // 建议意图动作
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; // 建议意图数据
+ 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_FOLER // 且不在回收站中
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 且类型为笔记
+ " FROM " + TABLE.NOTE // 从笔记表
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 条件摘要LIKE参数(模糊搜索)
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER // 且不在回收站中
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 且类型为笔记
/**
*
*
* @return true
* @return true
*/
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助类实例(单例
return true; // 返回true表示创建成功
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助类实例(单例模式
return true; // 创建成功
}
/**
*
* URI
* URI
* @param uri URI
* @param projection
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return
* @param selectionArgs
* @param sortOrder
* @return
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null; // 游标变量
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库
String id = null; // ID变量
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); // 执行带ID的查询
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); // 执行带ID的查询
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; // 游标,用于返回查询结果
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); // 获取查询词
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");
}
} else {
// 从查询参数获取搜索模式
searchString = uri.getQueryParameter("pattern"); // 获取pattern参数
}
if (TextUtils.isEmpty(searchString)) { // 如果搜索字符串为空
return null; // 返回null
}
if (TextUtils.isEmpty(searchString)) { // 如果搜索字符串为空
return null; // 返回空
}
try {
searchString = String.format("%%%s%%", searchString); // 格式化为LIKE模式%keyword%
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用于数据变化通知
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
* URI
* @param uri URI
* @param values
* @return URI
* @param values
* @return URIID
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
long dataId = 0, noteId = 0, insertedId = 0; // 插入的ID
switch (mMatcher.match(uri)) {
case URI_NOTE: // 插入笔记
insertedId = noteId = db.insert(TABLE.NOTE, null, values); // 插入笔记返回ID
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); // 插入数据返回ID
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变化
}
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); // 通知数据URI变化
}
// 通知数据URI变化(如果插入了数据)
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId); // 返回新插入项的URI
return ContentUris.withAppendedId(uri, insertedId); // 返回插入的URI包含新记录的ID
} catch (Exception e) {
Log.e(TAG, "Error inserting data: " + e.toString());
return null;
}
}
/**
*
* URI
* URI
* @param uri URI
* @param selection
* @param selectionArgs
* @param selectionArgs
* @return
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0; // 删除计数
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 "; // 添加ID>0条件
count = db.delete(TABLE.NOTE, selection, selectionArgs); // 执行删除
break;
case URI_NOTE_ITEM: // 删除单个笔记
id = uri.getPathSegments().get(1); // 获取ID
long noteId = Long.valueOf(id); // 转换为长整型
if (noteId <= 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变化
}
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); // 获取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变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变化
} catch (Exception e) {
Log.e(TAG, "Error deleting data: " + e.toString());
}
return count; // 返回删除计数
}
/**
*
* URI
* URI
* @param uri URI
* @param values
* @param values
* @param selection
* @param selectionArgs
* @param selectionArgs
* @return
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0; // 更新计数
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); // 获取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); // 获取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异常
}
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变化
if (count > 0) { // 如果更新了数据
if (updateData) { // 如果是更新数据
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变化
} catch (Exception e) {
Log.e(TAG, "Error updating data: " + e.toString());
}
return count; // 返回更新计数
}
/**
*
* AND
* AND
* @param selection
* @return
*/
@ -332,42 +354,35 @@ public class NotesProvider extends ContentProvider {
/**
*
*
*
* @param id ID-1使
* @param selection
* @param selectionArgs
*/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120); // 构建SQL语句
sql.append("UPDATE "); // UPDATE关键字
sql.append(TABLE.NOTE); // 表名
sql.append(" SET "); // SET关键字
sql.append(NoteColumns.VERSION); // 版本字段
sql.append("=" + NoteColumns.VERSION + "+1 "); // 版本号加1
if (id > 0 || !TextUtils.isEmpty(selection)) { // 如果有条件
sql.append(" WHERE "); // WHERE关键字
}
if (id > 0) { // 如果有指定ID
sql.append(NoteColumns.ID + "=" + String.valueOf(id)); // ID条件
}
if (!TextUtils.isEmpty(selection)) { // 如果有选择语句
String selectString = id > 0 ? parseSelection(selection) : selection; // 解析选择语句
// 替换参数占位符
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args); // 替换问号为实际参数
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); // 使用选择语句更新多个笔记版本
}
sql.append(selectString); // 添加选择条件
db.execSQL(sql.toString()); // 执行SQL语句
} catch (Exception e) {
Log.e(TAG, "Error increasing note version: " + e.toString());
}
mHelper.getWritableDatabase().execSQL(sql.toString()); // 执行SQL
}
/**
* MIME
* null
* @param uri URI
* @return MIME
* @return MIME
*/
@Override
public String getType(Uri uri) {

@ -24,65 +24,105 @@ 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();
public class MetaData extends Task { // 元数据类继承自Task
private final static String TAG = MetaData.class.getSimpleName(); // 日志标签
private String mRelatedGid = null; // 关联的Google任务ID
// 关联的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 {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); // 在元数据中添加Google任务ID
// 将Google Task ID添加到元信息中
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid"); // 添加失败
Log.e(TAG, "failed to put related gid");
}
setNotes(metaInfo.toString()); // 将元数据JSON字符串设为备注
setName(GTaskStringUtils.META_NOTE_NAME); // 设置名称为元数据笔记名称
// 将元信息JSON字符串设置为笔记内容
setNotes(metaInfo.toString());
// 设置任务名称为元数据笔记的特定名称
setName(GTaskStringUtils.META_NOTE_NAME);
}
// 获取关联的Google任务ID
/**
* Google Task ID
* @return Google Task ID
*/
public String getRelatedGid() {
return mRelatedGid;
}
// 重写:检查是否值得保存(只要有备注就值得保存)
/**
*
*
* @return
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null; // 只要有备注就值得保存
return getNotes() != null;
}
// 重写从远程JSON设置内容
/**
* JSON
* JSONGoogle Task ID
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js); // 调用父类方法
if (getNotes() != null) { // 如果有备注
super.setContentByRemoteJSON(js); // 调用父类方法设置基础内容
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim()); // 解析备注为JSON
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); // 获取关联的Google任务ID
// 从笔记内容中解析元信息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
Log.w(TAG, "failed to get related gid");
mRelatedGid = null; // 解析失败时为null
}
}
}
// 重写从本地JSON设置内容不应该被调用
/**
* JSON
* MetaDataJSON
* @param js JSON
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
// 这个函数不应该被调用
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); // 抛出异常
// 此函数不应被调用因为MetaData不应从本地JSON加载
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
// 重写从内容生成本地JSON不应该被调用
/**
* JSON
* MetaDataJSON
* @return JSON
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); // 抛出异常
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"); // 抛出异常
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -20,8 +20,13 @@ import android.database.Cursor;
import org.json.JSONObject;
public abstract class Node { // 抽象节点类,所有数据节点的基类
// 同步操作类型常量
/**
* 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; // 添加到本地
@ -29,76 +34,134 @@ public abstract class Node { // 抽象节点类,所有数据节点的基类
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_UPDATE_CONFLICT = 7; // 更新冲突
public static final int SYNC_ACTION_ERROR = 8; // 同步错误
private String mGid; // Google任务ID
private String mName; // 节点名称
private long mLastModified; // 最后修改时间
private boolean mDeleted; // 删除标记
// 构造函数
// Google Task ID
private String mGid;
// 节点名称
private String mName;
// 最后修改时间戳
private long mLastModified;
// 是否已删除
private boolean mDeleted;
/**
*
*
*/
public Node() {
mGid = null; // Google任务ID为空
mName = ""; // 名称为空字符串
mLastModified = 0; // 最后修改时间为0
mDeleted = false; // 未删除
mGid = null; // Google Task ID初始为null
mName = ""; // 名称初始为空字符串
mLastModified = 0; // 最后修改时间初始为0
mDeleted = false; // 删除标志初始为false
}
// 抽象方法获取创建操作的JSON对象
/**
* JSON
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getCreateAction(int actionId);
// 抽象方法获取更新操作的JSON对象
/**
* JSON
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getUpdateAction(int actionId);
// 抽象方法从远程JSON设置内容
/**
* JSON
*
* @param js JSON
*/
public abstract void setContentByRemoteJSON(JSONObject js);
// 抽象方法从本地JSON设置内容
/**
* JSON
*
* @param js JSON
*/
public abstract void setContentByLocalJSON(JSONObject js);
// 抽象方法从内容生成本地JSON
/**
* JSON
*
* @return JSON
*/
public abstract JSONObject getLocalJSONFromContent();
// 抽象方法:根据游标获取同步操作类型
/**
*
*
* @param c
* @return
*/
public abstract int getSyncAction(Cursor c);
// 设置Google任务ID
/**
* 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任务ID
/**
* 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,153 +34,222 @@ 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(); // 日志标签,使用类名
// 日志标签
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999; // 无效ID标识
// 无效ID常量
private static final int INVALID_ID = -99999;
// 查询数据库时使用的列投影(需要查询的列)
/**
*
*
*/
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
DataColumns.ID, // 数据ID
DataColumns.MIME_TYPE, // MIME类型
DataColumns.CONTENT, // 内容
DataColumns.DATA1, // 数据列1
DataColumns.DATA3 // 数据列3
};
public static final int DATA_ID_COLUMN = 0; // ID列在游标中的索引
public static final int DATA_MIME_TYPE_COLUMN = 1; // MIME类型列索引
public static final int DATA_CONTENT_COLUMN = 2; // 内容列索引
public static final int DATA_CONTENT_DATA_1_COLUMN = 3; // DATA1列索引
public static final int DATA_CONTENT_DATA_3_COLUMN = 4; // DATA3列索引
private ContentResolver mContentResolver; // 内容解析器,用于数据库操作
private boolean mIsCreate; // 是否为新建数据记录
private long mDataId; // 数据记录ID
private String mDataMimeType; // MIME类型
private String mDataContent; // 数据内容
private long mDataContentData1; // 数据字段1长整型
private String mDataContentData3; // 数据字段3字符串
private ContentValues mDiffDataValues; // 存储需要更新的字段差异值
// 构造函数创建新的SqlData对象未存入数据库
// 数据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(); // 初始化差异值容器
mContentResolver = context.getContentResolver();
mIsCreate = true; // 标记为新记录
mDataId = INVALID_ID; // 初始化为无效ID
mDataMimeType = DataConstants.NOTE; // 默认MIME类型为笔记
mDataContent = ""; // 内容初始化为空字符串
mDataContentData1 = 0; // 数据列1初始化为0
mDataContentData3 = ""; // 数据列3初始化为空字符串
mDiffDataValues = new ContentValues(); // 初始化差异值容器
}
// 构造函数从数据库游标创建SqlData对象
/**
* -
* @param context
* @param c
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = false; // 标记为已存在数据库
loadFromCursor(c); // 从游标加载数据
mDiffDataValues = new ContentValues(); // 初始化差异值容器
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); // 获取DATA1
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 获取DATA3
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
* 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) { // 如果是新建或ID不同
mDiffDataValues.put(DataColumns.ID, dataId); // 记录ID差异
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId); // 记录变化
}
mDataId = dataId; // 更新当前ID
mDataId = dataId; // 更新当前
// 处理MIME类型
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { // 如果是新建或MIME类型不同
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 记录MIME类型差异
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 记录变化
}
mDataMimeType = dataMimeType; // 更新MIME类型
mDataMimeType = dataMimeType; // 更新当前值
// 处理内容
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) { // 如果是新建或内容不同
mDiffDataValues.put(DataColumns.CONTENT, dataContent); // 记录内容差异
if (mIsCreate || !mDataContent.equals(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) { // 如果是新建或DATA1不同
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 记录DATA1差异
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 记录变化
}
mDataContentData1 = dataContentData1; // 更新DATA1
mDataContentData1 = dataContentData1; // 更新当前值
// 处理数据列3
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { // 如果是新建或DATA3不同
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 记录DATA3差异
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 记录变化
}
mDataContentData3 = dataContentData3; // 更新DATA3
mDataContentData3 = dataContentData3; // 更新当前值
}
// 将对象内容转换为JSON格式
/**
* JSON
* @return JSON
* @throws JSONException JSON
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) { // 如果对象是新建的(未保存到数据库)
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null; // 返回null因为数据不存在
return null;
}
JSONObject js = new JSONObject(); // 创建JSON对象
js.put(DataColumns.ID, mDataId); // 添加ID字段
js.put(DataColumns.MIME_TYPE, mDataMimeType); // 添加MIME类型字段
js.put(DataColumns.CONTENT, mDataContent); // 添加内容字段
js.put(DataColumns.DATA1, mDataContentData1); // 添加DATA1字段
js.put(DataColumns.DATA3, mDataContentData3); // 添加DATA3字段
return js; // 返回JSON对象
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 (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); // 插入数据
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); // 插入数据
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1)); // 从URI获取新生成的ID
// 从返回的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"); // 抛出异常
throw new ActionFailureException("create note failed");
}
} else { // 如果是更新数据
if (mDiffDataValues.size() > 0) { // 如果有需要更新的字段
} else {
// 现有记录:更新操作
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) { // 如果不验证版本
// 直接更新数据
if (!validateVersion) {
// 不验证版本号:直接更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else { // 如果需要验证版本
// 带版本控制的更新(防止同步冲突)
} 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)
});
}
if (result == 0) { // 如果没有更新任何行
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
// 重置状态
mDiffDataValues.clear(); // 清空差异值
mIsCreate = false; // 标记为已创建
mIsCreate = false; // 标记为现有记录
}
// 获取数据ID
/**
* ID
* @return ID
*/
public long getId() {
return mDataId;
}

@ -37,303 +37,390 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* SqlNote
* SQLiteJSON
*
*/
public class SqlNote {
private static final String TAG = SqlNote.class.getSimpleName(); // 日志标签
// 日志标签
private static final String TAG = SqlNote.class.getSimpleName();
private static final int INVALID_ID = -99999; // 无效ID标识
// 无效ID常量
private static final int INVALID_ID = -99999;
// 笔记查询的列投影
/**
*
*
*/
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE,
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
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; // 是否为新建笔记
private long mId; // 笔记ID
private long mAlertDate; // 提醒日期
private int mBgColorId; // 背景颜色ID
private long mCreatedDate; // 创建日期
private int mHasAttachment; // 是否有附件
private long mModifiedDate; // 修改日期
private long mParentId; // 父笔记ID
private String mSnippet; // 内容摘要
private int mType; // 笔记类型(笔记/文件夹/系统)
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private long mOriginParent; // 原始父笔记ID
private long mVersion; // 版本号
private ContentValues mDiffNoteValues; // 笔记差异值
private ArrayList<SqlData> mDataList; // 笔记关联的数据列表
// 构造函数:创建新笔记
// 上下文对象
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; // 标记为新创建
mIsCreate = true; // 标记为新笔记
mId = INVALID_ID; // 初始化为无效ID
mAlertDate = 0; // 提醒日期为0
mBgColorId = ResourceParser.getDefaultBgId(context); // 获取默认背景颜色
mAlertDate = 0; // 默认无提醒
mBgColorId = ResourceParser.getDefaultBgId(context); // 默认背景颜色
mCreatedDate = System.currentTimeMillis(); // 创建时间为当前时间
mHasAttachment = 0; // 默认无附件
mModifiedDate = System.currentTimeMillis(); // 修改时间为当前时间
mParentId = 0; // 父笔记ID为0根目录
mSnippet = ""; // 摘要为空
mType = Notes.TYPE_NOTE; // 类型为普通笔记
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // 小部件ID无效
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 小部件类型无效
mOriginParent = 0; // 原始父笔记ID为0
mVersion = 0; // 版本号为0
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; // 标记为已存在
mIsCreate = false; // 标记为现有笔记
loadFromCursor(c); // 从游标加载笔记信息
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE) // 如果是普通笔记类型
loadDataContent(); // 加载关联的数据内容
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 如果是笔记类型,加载关联的数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
}
// 构造函数从笔记ID创建笔记对象
/**
* - ID
* @param context
* @param id ID
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为已存在
loadFromCursor(id); // ID加载笔记信息
mIsCreate = false; // 标记为现有笔记
loadFromCursor(id); // 根据ID加载笔记信息
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE) // 如果是普通笔记类型
loadDataContent(); // 加载关联的数据内容
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 如果是笔记类型,加载关联的数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值容器
}
// 从笔记ID加载笔记信息
/**
* 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);
new String[] {
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext(); // 移动到第一行
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); // 加载笔记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(); // 清空数据列表
mDataList.clear(); // 清空现有数据列表
try {
// 查询该笔记的所有关联数据
// 查询关联数据记录
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] { String.valueOf(mId) }, null);
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) { // 如果没有数据
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
while (c.moveToNext()) { // 遍历所有数据行
// 遍历所有数据记录
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c); // 创建SqlData对象
mDataList.add(data); // 添加到列表
mDataList.add(data); // 添加到数据列表
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close(); // 关闭游标
c.close(); // 确保关闭游标
}
}
// 从JSON对象设置笔记内容
/**
* 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) { // 如果是普通文件夹
// 对于文件夹,只能更新摘要和类型
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); // 记录摘要差异
if (mIsCreate || !mSnippet.equals(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); // 记录类型差异
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type); // 记录变化
}
mType = type; // 更新类型
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { // 如果是普通笔记
mType = type; // 更新当前值
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
// 处理笔记:更新所有字段
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
// 更新笔记ID
// 处理笔记ID
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
// 处理背景颜色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
// 处理父节点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
// 处理小部件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
// 处理原始父节点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;
if (data.has(DataColumns.ID)) { // 如果数据有ID
// 根据数据ID查找现有的SqlData对象
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
// 在现有数据列表中查找匹配的数据
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp; // 找到匹配的数据
sqlData = temp; // 找到现有对象
break;
}
}
}
if (sqlData == null) { // 如果没有找到匹配的数据
sqlData = new SqlData(mContext); // 创建新数据对象
// 如果没找到创建新的SqlData对象
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData); // 添加到数据列表
}
@ -343,24 +430,27 @@ public class SqlNote {
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false; // 返回失败
return false;
}
return true; // 返回成功
return true;
}
// 将笔记内容转换为JSON格式
/**
* JSON
* @return JSON
*/
public JSONObject getContent() {
try {
JSONObject js = new JSONObject(); // 创建JSON对象
JSONObject js = new JSONObject();
if (mIsCreate) { // 如果是新建笔记(未保存)
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null; // 返回null
return null;
}
JSONObject note = new JSONObject(); // 创建笔记对象
if (mType == Notes.TYPE_NOTE) { // 如果是普通笔记
// 添加所有笔记字段到JSON
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);
@ -373,134 +463,173 @@ 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); // 添加笔记元数据
// 处理关联的数据
JSONArray dataArray = new JSONArray(); // 创建数据数组
for (SqlData sqlData : mDataList) { // 遍历所有关联数据
JSONObject data = sqlData.getContent(); // 获取数据JSON
if (data != null) { // 如果数据不为空
dataArray.put(data); // 添加到数
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) { // 如果是文件夹
// 文件夹只有ID、类型和摘要字段
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; // 返回JSON对象
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null; // 返回null
return null;
}
// 设置父笔记ID
/**
* ID
* @param id ID
*/
public void setParentId(long id) {
mParentId = id; // 更新父笔记ID
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 记录差异
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 记录变化
}
// 设置Google任务ID
/**
* Google Task ID
* @param gid Google Task ID
*/
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 记录Google任务ID差异
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 记录变化
}
// 设置同步ID
/**
* ID
* @param syncId ID
*/
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 记录同步ID差异
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 记录变化
}
// 重置本地修改标记
/**
*
* 0
*/
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 将本地修改标记设为0
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 记录变化
}
// 获取笔记ID
/**
* ID
* @return ID
*/
public long getId() {
return mId;
}
// 获取父笔记ID
/**
* 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 (mIsCreate) {
// 新笔记:插入操作
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID); // 移除无效ID
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); // 插入数据库
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); // 插入笔记
try {
mId = Long.valueOf(uri.getPathSegments().get(1)); // 从URI获取新生成的ID
// 从返回的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"); // 抛出异常
throw new ActionFailureException("create note failed");
}
if (mId == 0) { // 如果ID为0创建失败
throw new IllegalStateException("Create thread id 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); // 提交数据到数据库
// 如果是笔记类型,提交关联的数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1); // 提交数据,不验证版本
}
}
} else { // 如果是更新笔记
// 验证笔记ID有效性
} 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"); // 抛出异常
throw new IllegalStateException("Try to update note with invalid id");
}
if (mDiffNoteValues.size() > 0) { // 如果有需要更新的字段
mVersion++; // 增加版本号
if (mDiffNoteValues.size() > 0) {
mVersion ++; // 增加版本号
int result = 0;
if (!validateVersion) { // 如果不验证版本
// 直接更新笔记
if (!validateVersion) {
// 不验证版本号:直接更新
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] { String.valueOf(mId) });
} else { // 如果需要验证版本
// 带版本控制的更新(防止同步冲突)
+ 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) });
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) { // 如果没有更新任何行
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); // 提交数据到数据库
// 如果是笔记类型,提交关联的数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion); // 提交数据,验证版本
}
}
}
// 刷新本地信息
loadFromCursor(mId); // 重新加载笔记信息
if (mType == Notes.TYPE_NOTE) // 如果是普通笔记
loadFromCursor(mId); // 重新从数据库加载笔记信息
if (mType == Notes.TYPE_NOTE)
loadDataContent(); // 重新加载关联数据
// 重置状态
mDiffNoteValues.clear(); // 清空差异值
mIsCreate = false; // 标记为已创建
mIsCreate = false; // 标记为现有笔记
}
}

@ -31,63 +31,81 @@ 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; // 任务备注
private JSONObject mMetaInfo; // 元数据信息(存储笔记的完整信息)
private Task mPriorSibling; // 前一个兄弟任务(用于排序)
private TaskList mParent; // 父任务列表
// 构造函数
// 日志标签
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; // 元数据为空
super();
mCompleted = false; // 默认未完成
mNotes = null; // 默认无备注
mPriorSibling = null; // 默认无前兄弟
mParent = null; // 默认无父列表
mMetaInfo = null; // 默认无元信息
}
// 获取创建任务的JSON操作对象
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建JSON对象
JSONObject js = new JSONObject();
try {
// 设置操作类型为创建
// 动作类型:创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置操作ID
// 作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()); // 添加备注
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 实体类型任务
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 任务备注
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 添加实体数据
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// 设置父任务列表ID
// 父节点ID
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// 设置目标父类型为组(任务列表)
// 目标父节点类型
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 设置列表ID
// 列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// 如果有前兄弟任务设置前兄弟任务ID
// 前兄弟节点ID用于确定插入位置
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
@ -95,108 +113,123 @@ public class Task extends Node {
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject"); // 抛出异常
throw new ActionFailureException("fail to generate task-create jsonobject");
}
return js; // 返回JSON对象
return js;
}
// 获取更新任务的JSON操作对象
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建JSON对象
JSONObject js = new JSONObject();
try {
// 设置操作类型为更新
// 动作类型:更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置操作ID
// 作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务ID
// 任务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()); // 添加备注
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); // 添加实体数据
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"); // 抛出异常
throw new ActionFailureException("fail to generate task-update jsonobject");
}
return js; // 返回JSON对象
return js;
}
// 从远程JSON设置任务内容从Google Tasks API获取的数据
/**
* 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));
}
// 设置最后修改时间
// 最后修改时间
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"); // 抛出异常
throw new ActionFailureException("fail to get task content from jsonobject");
}
}
}
// 从本地JSON设置任务内容从数据库获取的数据
/**
* 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"); // 没有可用数据
return;
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记元数据
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
// 解析笔记信息
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { // 如果不是普通笔记类型
// 验证笔记类型
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
Log.e(TAG, "invalid type");
return;
}
// 遍历数据数组,找到类型为NOTE的数据作为任务名称
// 遍历数据数组,查找笔记内容
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)); // 设置任务名称
// 将笔记内容设置为任务名称
setName(data.getString(DataColumns.CONTENT));
break;
}
}
@ -207,14 +240,19 @@ public class Task extends Node {
}
}
// 从任务内容生成本地JSON用于保存到数据库
/**
* JSON
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
String name = getName(); // 获取任务名称
String name = getName();
try {
if (mMetaInfo == null) { // 如果没有元数据信息(从网页创建的新任务)
if (name == null) { // 如果名称为空
if (mMetaInfo == null) {
// 从Web创建的新任务无元信息
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null; // 返回null
return null;
}
// 创建新的JSON结构
@ -222,63 +260,75 @@ public class Task extends Node {
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; // 返回JSON对象
} else { // 如果已有元数据信息(已同步的任务)
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记元数据
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
// 更新数据数组中的任务名称
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()); // 更新任务名称
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());
e.printStackTrace();
return null; // 返回null
return null;
}
}
// 设置元数据信息
/**
*
* MetaData
* @param metaData MetaData
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
mMetaInfo = new JSONObject(metaData.getNotes()); // 从备注解析JSON
// 从备注中解析元信息JSON
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null; // 解析失败则设为null
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); // 获取笔记信息
// 从元信息中获取笔记信息
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) { // 如果没有笔记信息
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)) { // 如果没有笔记ID
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL; // 需要更新本地
return SYNC_ACTION_UPDATE_LOCAL; // 远程笔记ID被删除需要更新本地
}
// 验证笔记ID是否匹配
@ -287,22 +337,29 @@ public class Task extends Node {
return SYNC_ACTION_UPDATE_LOCAL; // ID不匹配需要更新本地
}
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { // 如果本地没有修改
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { // 如果同步ID等于最后修改时间
return SYNC_ACTION_NONE; // 无需同步
// 检查本地修改标志
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; // 需要更新本地
// 将远程更新应用到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else { // 如果本地有修改
// 验证Google任务ID是否匹配
} 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()) { // 如果同步ID等于最后修改时间
return SYNC_ACTION_UPDATE_REMOTE; // 只需要更新远程
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 只有本地有修改
return SYNC_ACTION_UPDATE_REMOTE;
} else {
return SYNC_ACTION_UPDATE_CONFLICT; // 有冲突
// 两端都有修改,发生冲突
return SYNC_ACTION_UPDATE_CONFLICT;
}
}
} catch (Exception e) {
@ -310,51 +367,49 @@ 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;
}
// 设置任务备注
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;
}

@ -29,97 +29,121 @@ import org.json.JSONObject;
import java.util.ArrayList;
public class TaskList extends Node { // 任务列表类继承自Node
private static final String TAG = TaskList.class.getSimpleName(); // 日志标签
private int mIndex; // 列表索引
private ArrayList<Task> mChildren; // 子任务列表
// 构造函数
/**
* 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(); // 调用父类构造函数
super();
mChildren = new ArrayList<Task>(); // 初始化子任务列表
mIndex = 1; // 默认索引为1
}
// 获取创建任务列表的JSON操作对象
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建JSON对象
JSONObject js = new JSONObject();
try {
// 设置操作类型为创建
// 动作类型:创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置操作ID
// 作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); // 添加实体数据
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"); // 抛出异常
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
}
return js; // 返回JSON对象
return js;
}
// 获取更新任务列表的JSON操作对象
/**
* JSON
* Google Tasks
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建JSON对象
JSONObject js = new JSONObject();
try {
// 设置操作类型为更新
// 动作类型:更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置操作ID
// 作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务列表ID
// 列表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); // 添加实体数据
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"); // 抛出异常
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
}
return js; // 返回JSON对象
return js;
}
// 从远程JSON设置任务列表内容
/**
* 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));
}
// 设置最后修改时间
// 最后修改时间
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));
}
@ -127,34 +151,41 @@ public class TaskList extends Node { // 任务列表类继承自Node
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject"); // 抛出异常
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
}
}
// 从本地JSON设置任务列表内容
/**
* 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"); // 没有可用数据
return;
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取文件夹信息
// 解析文件夹信息
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { // 如果是普通文件夹
String name = folder.getString(NoteColumns.SNIPPET); // 获取文件夹名称
// 根据文件夹类型设置名称
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) // 通话记录文件夹
} 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);
+ GTaskStringUtils.FOLDER_CALL_NOTE); // 通话记录文件夹
else
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());
@ -162,52 +193,71 @@ public class TaskList extends Node { // 任务列表类继承自Node
}
}
// 从任务列表内容生成本地JSON
/**
* JSON
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject(); // 创建JSON对象
JSONObject folder = new JSONObject(); // 创建文件夹对象
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
String folderName = getName(); // 获取文件夹名称
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) // 如果以MIUI前缀开头
// 处理文件夹名称移除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); // 类型为系统文件夹
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); // 类型为普通文件夹
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 普通文件夹
js.put(GTaskStringUtils.META_HEAD_NOTE, folder); // 添加文件夹信息
js.put(GTaskStringUtils.META_HEAD_NOTE, folder); // 添加到JSON
return js; // 返回JSON对象
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null; // 返回null
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()) { // 如果同步ID等于最后修改时间
return SYNC_ACTION_NONE; // 无需同步
// 检查本地修改标志
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; // 需要更新本地
// 将远程更新应用到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else { // 如果本地有修改
// 验证Google任务ID是否匹配
} 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()) { // 如果同步ID等于最后修改时间
return SYNC_ACTION_UPDATE_REMOTE; // 只需要更新远程
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 只有本地有修改
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// 对于文件夹冲突,直接应用本地修改
// 对于文件夹冲突,优先应用本地修改
return SYNC_ACTION_UPDATE_REMOTE;
}
}
@ -216,141 +266,184 @@ public class TaskList extends Node { // 任务列表类继承自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); // 添加到列表
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); // 父任务列表为当前列表
.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()) { // 检查索引有效性
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); // 在指定位置添加
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task); // 插入到指定位置
// 更新任务列表关系
// 更新任务列表的前兄弟关系
Task preTask = null;
Task afterTask = null;
if (index != 0) // 如果不是第一个
if (index != 0)
preTask = mChildren.get(index - 1); // 前一个任务
if (index != mChildren.size() - 1) // 如果不是最后一个
if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1); // 后一个任务
task.setPriorSibling(preTask); // 设置前兄弟任务
if (afterTask != null) // 如果有后一个任务
afterTask.setPriorSibling(task); // 更新后一个任务的前兄弟任务
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) { // 如果任务在列表中
int index = mChildren.indexOf(task);
if (index != -1) {
ret = mChildren.remove(task); // 移除任务
if (ret) {
// 重置前兄弟任务和父任务列表
task.setPriorSibling(null); // 前兄弟任务设为null
task.setParent(null); // 父任务列表设为null
// 重置任务的前兄弟节点和父节点
task.setPriorSibling(null);
task.setParent(null);
// 更新任务列表
if (index != mChildren.size()) { // 如果不是最后一个
if (index != mChildren.size()) {
// 更新被移除任务后一个任务的前兄弟节点
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1)); // 更新前兄弟任务
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()) { // 检查索引有效性
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
}
int pos = mChildren.indexOf(task); // 获取任务当前位置
if (pos == -1) { // 如果任务不在列表中
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) // 如果位置相同
if (pos == index) // 位置未变
return true;
return (removeChildTask(task) && addChildTask(task, index)); // 先移除再添加
// 先移除再添加到新位置
return (removeChildTask(task) && addChildTask(task, index));
}
// 根据Google任务ID查找子任务
/**
* 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)) { // 如果ID匹配
return t; // 返回任务
if (t.getGid().equals(gid)) {
return t;
}
}
return null; // 没找到返回null
return null;
}
// 获取子任务在列表中的索引
/**
*
* @param task
* @return -1
*/
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task); // 返回任务索引
return mChildren.indexOf(task);
}
// 根据索引获取子任务
/**
*
* @param index
* @return null
*/
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) { // 检查索引有效性
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
return null; // 返回null
return null;
}
return mChildren.get(index); // 返回任务
return mChildren.get(index);
}
// 根据Google任务ID获取子任务
/**
* Google Task ID
* @param gid Google Task ID
* @return null
*/
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) { // 遍历所有任务
if (task.getGid().equals(gid)) // 如果ID匹配
return task; // 返回任务
for (Task task : mChildren) {
if (task.getGid().equals(gid))
return task;
}
return null; // 没找到返回null
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;
}

@ -16,53 +16,18 @@
package net.micode.notes.gtask.exception;
/**
* ActionFailureException
* Google Tasks
*
* RuntimeException
*
*
*
* 1. Google Task
* 2. JSON
* 3.
* 4.
*/
public class ActionFailureException extends RuntimeException {
/**
* UID
*
*/
private static final long serialVersionUID = 4425249765923293627L;
/**
*
* ActionFailureException
*/
public ActionFailureException() {
super(); // 调用父类RuntimeException的无参构造函数
super();
}
/**
*
* ActionFailureException
*
* @param paramString
*/
public ActionFailureException(String paramString) {
super(paramString); // 调用父类RuntimeException的带消息构造函数
super(paramString);
}
/**
*
* ActionFailureException
*
*
* @param paramString
* @param paramThrowable
*/
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable); // 调用父类RuntimeException的带消息和原因的构造函数
super(paramString, paramThrowable);
}
}
}

@ -16,55 +16,18 @@
package net.micode.notes.gtask.exception;
/**
* NetworkFailureException
* Google Tasks
*
* Exception
*
*
*
* 1.
* 2. 404500
* 3. SSL/TLS
* 4.
* 5.
*/
public class NetworkFailureException extends Exception {
/**
* UID
*
*/
private static final long serialVersionUID = 2107610287180234136L;
/**
*
* NetworkFailureException
*/
public NetworkFailureException() {
super(); // 调用父类Exception的无参构造函数
super();
}
/**
*
* NetworkFailureException
*
* @param paramString
* "网络连接失败""服务器无响应"
*/
public NetworkFailureException(String paramString) {
super(paramString); // 调用父类Exception的带消息构造函数
super(paramString);
}
/**
*
* NetworkFailureException
*
*
* @param paramString
* @param paramThrowable IOException
*/
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable); // 调用父类Exception的带消息和原因的构造函数
super(paramString, paramThrowable);
}
}
}

@ -1,3 +1,4 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
@ -29,112 +30,116 @@ import net.micode.notes.ui.NotesPreferenceActivity;
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 同步通知的ID确保唯一性
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 同步完成回调接口
public interface OnCompleteListener {
void onComplete(); // 同步完成时调用
void onComplete();
}
private Context mContext; // 上下文对象
private NotificationManager mNotifiManager; // 通知管理器
private GTaskManager mTaskManager; // 任务管理器
private OnCompleteListener mOnCompleteListener; // 完成监听器
private Context mContext;
private NotificationManager mNotifiManager;
private GTaskManager mTaskManager;
private OnCompleteListener mOnCompleteListener;
// 构造函数
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context; // 保存上下文
mOnCompleteListener = listener; // 保存完成监听器
mContext = context;
mOnCompleteListener = listener;
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE); // 获取通知管理器服务
mTaskManager = GTaskManager.getInstance(); // 获取任务管理器单例
.getSystemService(Context.NOTIFICATION_SERVICE);
mTaskManager = GTaskManager.getInstance();
}
// 取消同步操作
public void cancelSync() {
mTaskManager.cancelSync(); // 委托给任务管理器取消同步
mTaskManager.cancelSync();
}
// 发布同步进度
public void publishProgess(String message) {
publishProgress(new String[] {
message // 发布单个进度消息
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) {
// 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); // 执行同步操作并返回结果状态
.getSyncAccountName(mContext)));
return mTaskManager.sync(mContext, this);
}
// 更新同步进度
@Override
protected void onProgressUpdate(String... progress) {
showNotification(R.string.ticker_syncing, progress[0]); // 显示同步中的通知
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()); // 更新最后同步时间
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)); // 显示网络错误通知
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)); // 显示内部错误通知
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)); // 显示取消通知
.getString(R.string.error_sync_cancelled));
}
// 通知监听器同步已完成
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete(); // 在新线程中调用完成回调
mOnCompleteListener.onComplete();
}
}).start();
}
}
}
}

@ -62,533 +62,524 @@ import java.util.zip.InflaterInputStream;
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName(); // 日志标签
private static final String TAG = GTaskClient.class.getSimpleName();
// Google Tasks API URL常量
private static final String GTASK_URL = "https://mail.google.com/tasks/";
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // 获取数据URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // 提交数据URL
private static GTaskClient mInstance = null; // 单例实例
// HTTP客户端和相关参数
private DefaultHttpClient mHttpClient; // HTTP客户端实例
private String mGetUrl; // 动态生成的GET URL
private String mPostUrl; // 动态生成的POST URL
private long mClientVersion; // 客户端版本号,从服务器获取
private boolean mLoggedin; // 登录状态标志
private long mLastLoginTime; // 上次登录时间
private int mActionId; // 操作ID计数器用于生成唯一的操作ID
private Account mAccount; // 当前登录的Google账户
private JSONArray mUpdateArray; // 批量更新操作的JSON数组
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; // 默认GET URL
mPostUrl = GTASK_POST_URL; // 默认POST URL
mClientVersion = -1; // 未初始化的版本号
mLoggedin = false; // 初始状态为未登录
mLastLoginTime = 0; // 初始登录时间为0
mActionId = 1; // 操作ID从1开始
mAccount = null; // 初始账户为空
mUpdateArray = 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(); // 第一次调用时创建实例
mInstance = new GTaskClient();
}
return mInstance;
}
// 登录方法,返回登录是否成功
public boolean login(Activity activity) {
// 检查Cookie是否过期假设5分钟后过期
final long interval = 1000 * 60 * 5; // 5分钟的毫秒数
// 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; // 如果超过5分钟标记为未登录
mLoggedin = false;
}
// 检查账户是否切换,需要重新登录
// need to re-login after account switch
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false; // 账户已切换,需要重新登录
mLoggedin = false;
}
if (mLoggedin) {
Log.d(TAG, "already logged in"); // 已经登录,直接返回
Log.d(TAG, "already logged in");
return true;
}
mLastLoginTime = System.currentTimeMillis(); // 更新最后登录时间
String authToken = loginGoogleAccount(activity, false); // 获取Google账户授权令牌
mLastLoginTime = System.currentTimeMillis();
String authToken = loginGoogleAccount(activity, false);
if (authToken == null) {
Log.e(TAG, "login google account failed"); // 获取授权令牌失败
Log.e(TAG, "login google account failed");
return false;
}
// 如果不是gmail.com或googlemail.com账户尝试使用自定义域名登录
// 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/"); // 构建自定义域名URL
int index = mAccount.name.indexOf('@') + 1; // 找到@符号位置
String suffix = mAccount.name.substring(index); // 提取域名后缀
url.append(suffix + "/"); // 添加域名后缀
mGetUrl = url.toString() + "ig"; // 设置自定义GET URL
mPostUrl = url.toString() + "r/ig"; // 设置自定义POST URL
if (tryToLoginGtask(activity, authToken)) { // 尝试使用自定义域名登录
mLoggedin = true; // 登录成功
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 如果自定义域名登录失败尝试使用Google官方URL登录
// try to login with google official url
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL; // 重置为默认GET URL
mPostUrl = GTASK_POST_URL; // 重置为默认POST URL
if (!tryToLoginGtask(activity, authToken)) { // 尝试使用官方URL登录
return false; // 登录失败
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true; // 标记为已登录
return true; // 登录成功
mLoggedin = true;
return true;
}
// 登录Google账户并获取授权令牌
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity); // 获取账户管理器
Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有Google账户
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google");
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account"); // 没有可用的Google账户
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)) { // 查找匹配的账户
if (a.name.equals(accountName)) {
account = a;
break;
}
}
if (account != null) {
mAccount = account; // 设置当前账户
mAccount = account;
} else {
Log.e(TAG, "unable to get an account with the same name in the settings"); // 未找到匹配账户
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); // 请求授权令牌
"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); // 重新获取令牌
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"); // 获取授权令牌失败
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken; // 返回授权令牌
return authToken;
}
// 尝试登录Google Tasks
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) { // 第一次登录尝试
// 授权令牌可能已过期,尝试重新获取令牌并登录
authToken = loginGoogleAccount(activity, true); // 重新获取授权令牌
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"); // 重新获取令牌失败
Log.e(TAG, "login google account failed");
return false;
}
if (!loginGtask(authToken)) { // 使用新令牌再次尝试登录
Log.e(TAG, "login gtask failed"); // 登录失败
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true; // 登录成功
return true;
}
// 实际的Google Tasks登录逻辑
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000; // 连接超时时间10秒
int timeoutSocket = 15000; // Socket超时时间15秒
HttpParams httpParameters = new BasicHttpParams(); // 创建HTTP参数
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); // 设置连接超时
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); // 设置Socket超时
mHttpClient = new DefaultHttpClient(httpParameters); // 创建HTTP客户端
BasicCookieStore localBasicCookieStore = new BasicCookieStore(); // 创建Cookie存储
mHttpClient.setCookieStore(localBasicCookieStore); // 设置Cookie存储
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // 禁用Expect-Continue
// 登录Google Tasks
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; // 构建登录URL
HttpGet httpGet = new HttpGet(loginUrl); // 创建GET请求
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet); // 执行HTTP请求
response = mHttpClient.execute(httpGet);
// 检查Cookie中是否包含认证信息
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); // 获取所有Cookie
// get the cookie now
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) { // 查找包含"GTL"的CookieGoogle Tasks认证
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie"); // 警告未找到认证Cookie
Log.w(TAG, "it seems that there is no auth cookie");
}
// 从响应中提取客户端版本号
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JavaScript响应开始标记
String jsEnd = ")}</script>"; // JavaScript响应结束标记
int begin = resString.indexOf(jsBegin); // 查找开始位置
int end = resString.lastIndexOf(jsEnd); // 查找结束位置
// 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); // 提取JavaScript字符串
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString); // 解析为JSON对象
mClientVersion = js.getLong("v"); // 获取客户端版本号
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v");
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
// 捕获所有其他异常
Log.e(TAG, "httpget gtask_url failed"); // HTTP GET请求失败
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
}
return true; // 登录成功
return true;
}
// 获取下一个操作ID
private int getActionId() {
return mActionId++; // 返回当前操作ID并递增
return mActionId++;
}
// 创建HTTP POST请求对象
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl); // 创建POST请求
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置内容类型
httpPost.setHeader("AT", "1"); // 设置AT头部认证令牌
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1");
return httpPost;
}
// 从HTTP实体获取响应内容支持GZIP和Deflate压缩
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue(); // 获取内容编码
Log.d(TAG, "encoding: " + contentEncoding); // 记录编码类型
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
InputStream input = entity.getContent(); // 获取输入流
InputStream input = entity.getContent();
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent()); // 如果是GZIP编码使用GZIP输入流
input = new GZIPInputStream(entity.getContent());
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true); // 创建Inflater对象
input = new InflaterInputStream(entity.getContent(), inflater); // 使用Inflater输入流
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(); // 创建字符串构建器
InputStreamReader isr = new InputStreamReader(input);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
while (true) {
String buff = br.readLine(); // 读取一行
String buff = br.readLine();
if (buff == null) {
return sb.toString(); // 读取完成,返回内容
return sb.toString();
}
sb = sb.append(buff); // 添加到字符串构建器
sb = sb.append(buff);
}
} finally {
input.close(); // 确保关闭输入流
input.close();
}
}
// 发送POST请求到Google Tasks API
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first"); // 未登录错误
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
HttpPost httpPost = createHttpPost(); // 创建POST请求
HttpPost httpPost = createHttpPost();
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>(); // 创建参数列表
list.add(new BasicNameValuePair("r", js.toString())); // 添加JSON参数
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建URL编码实体
httpPost.setEntity(entity); // 设置请求实体
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost); // 执行请求
String jsString = getResponseContent(response.getEntity()); // 获取响应内容
return new JSONObject(jsString); // 解析为JSON对象并返回
// 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()); // HTTP协议异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (IOException e) {
Log.e(TAG, e.toString()); // IO异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
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()); // 其他异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request");
}
}
// 创建任务
public void createTask(Task task) throws NetworkFailureException {
commitUpdate(); // 提交之前的更新
commitUpdate();
try {
JSONObject jsPost = new JSONObject(); // 创建POST请求JSON
JSONArray actionList = new JSONArray(); // 创建操作列表
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 添加创建任务的操作
actionList.put(task.getCreateAction(getActionId())); // 获取任务的创建操作
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 添加操作列表
// 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);
// 发送请求
JSONObject jsResponse = postRequest(jsPost); // 发送POST请求
// 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)); // 设置任务的GID
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON处理异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
// 创建任务列表
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate(); // 提交之前的更新
commitUpdate();
try {
JSONObject jsPost = new JSONObject(); // 创建POST请求JSON
JSONArray actionList = new JSONArray(); // 创建操作列表
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 添加创建任务列表的操作
actionList.put(tasklist.getCreateAction(getActionId())); // 获取任务列表的创建操作
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 添加操作列表
// 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);
// 发送请求
JSONObject jsResponse = postRequest(jsPost); // 发送POST请求
// 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)); // 设置任务列表的GID
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON处理异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
// 提交批量更新
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) { // 如果有待提交的更新
if (mUpdateArray != null) {
try {
JSONObject jsPost = new JSONObject(); // 创建POST请求JSON
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; // 清空更新数组
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON处理异常
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) {
// 如果更新数组过大超过10个先提交当前更新
// too many update items may result in an error
// set max to 10 items
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate(); // 提交当前更新
commitUpdate();
}
if (mUpdateArray == null) // 如果更新数组为空,创建新数组
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加节点的更新操作
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
// 移动任务
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate(); // 提交之前的更新
commitUpdate();
try {
JSONObject jsPost = new JSONObject(); // 创建POST请求JSON
JSONArray actionList = new JSONArray(); // 创建操作列表
JSONObject action = new JSONObject(); // 创建移动操作
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()); // 设置操作ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 设置任务ID
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) {
// 如果是在同一个任务列表中移动且任务不是第一个设置前一个兄弟节点的ID
// 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()); // 设置源列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 设置目标父节点ID
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
if (preParent != curParent) {
// 如果是在不同任务列表之间移动设置目标列表ID
// 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); // 添加操作列表到请求
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 添加客户端版本
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 发送请求
postRequest(jsPost);
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON处理异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed");
}
}
// 删除节点
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate(); // 提交之前的更新
commitUpdate();
try {
JSONObject jsPost = new JSONObject(); // 创建POST请求JSON
JSONArray actionList = new JSONArray(); // 创建操作列表
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 添加删除操作
node.setDeleted(true); // 标记节点为已删除
actionList.put(node.getUpdateAction(getActionId())); // 获取节点的更新操作(包含删除标记)
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 添加操作列表
// 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; // 清空更新数组
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON处理异常
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"); // 未登录错误
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
try {
HttpGet httpGet = new HttpGet(mGetUrl); // 创建GET请求
HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet); // 执行请求
// 从响应中提取任务列表数据
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JavaScript响应开始标记
String jsEnd = ")}</script>"; // JavaScript响应结束标记
int begin = resString.indexOf(jsBegin); // 查找开始位置
int end = resString.lastIndexOf(jsEnd); // 查找结束位置
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); // 提取JavaScript字符串
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString); // 解析为JSON对象
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); // 返回任务列表数组
JSONObject js = new JSONObject(jsString);
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString()); // HTTP协议异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (IOException e) {
Log.e(TAG, e.toString()); // IO异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
// 获取特定任务列表的所有任务
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate(); // 提交之前的更新
commitUpdate();
try {
JSONObject jsPost = new JSONObject(); // 创建POST请求JSON
JSONArray actionList = new JSONArray(); // 创建操作列表
JSONObject action = new JSONObject(); // 创建获取所有任务的操作
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()); // 设置操作ID
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 设置任务列表ID
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 设置不获取已删除的任务
actionList.put(action); // 将操作添加到操作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 添加操作列表到请求
// 添加客户端版本
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); // 返回任务数组
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON处理异常
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;
}
}
}

@ -24,112 +24,105 @@ import android.os.Bundle;
import android.os.IBinder;
public class GTaskSyncService extends Service {
// 同步操作类型常量
public final static String ACTION_STRING_NAME = "sync_action_type"; // Intent中操作类型的键名
// 同步操作类型值
public final static int ACTION_START_SYNC = 0; // 开始同步
public final static int ACTION_CANCEL_SYNC = 1; // 取消同步
public final static int ACTION_INVALID = 2; // 无效操作
// 广播相关常量
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; // 广播名称
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 是否正在同步的广播键
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 进度消息的广播键
private static GTaskASyncTask mSyncTask = null; // 同步任务实例
private static String mSyncProgress = ""; // 同步进度消息
// 开始同步方法
public final static String ACTION_STRING_NAME = "sync_action_type";
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) { // 如果没有正在进行的同步任务
// 创建新的同步任务,并设置完成监听器
if (mSyncTask == null) {
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
public void onComplete() {
mSyncTask = null; // 同步完成,清空任务引用
sendBroadcast(""); // 发送空广播通知同步完成
stopSelf(); // 停止服务
mSyncTask = null;
sendBroadcast("");
stopSelf();
}
});
sendBroadcast(""); // 发送广播通知开始同步
mSyncTask.execute(); // 执行同步任务
sendBroadcast("");
mSyncTask.execute();
}
}
// 取消同步方法
private void cancelSync() {
if (mSyncTask != null) { // 如果有正在进行的同步任务
mSyncTask.cancelSync(); // 取消同步
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
@Override
public void onCreate() {
mSyncTask = null; // 服务创建时初始化同步任务为空
mSyncTask = null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras(); // 获取Intent中的附加数据
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { // 如果包含操作类型
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { // 根据操作类型执行相应操作
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(); // 开始同步
startSync();
break;
case ACTION_CANCEL_SYNC:
cancelSync(); // 取消同步
cancelSync();
break;
default:
break; // 无效操作,不做任何处理
break;
}
return START_STICKY; // 返回粘性服务标志,系统会在服务被杀死后尝试重新创建
return START_STICKY;
}
return super.onStartCommand(intent, flags, startId); // 默认处理
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onLowMemory() {
if (mSyncTask != null) { // 在低内存情况下
mSyncTask.cancelSync(); // 取消同步任务以释放资源
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 服务绑定方法(未实现)
public IBinder onBind(Intent intent) {
return null; // 不提供绑定服务功能
return null;
}
// 发送广播方法
public void sendBroadcast(String msg) {
mSyncProgress = msg; // 更新同步进度消息
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建广播Intent
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 添加是否正在同步的附加信息
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加进度消息
sendBroadcast(intent); // 发送广播
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);
}
// 静态方法从Activity启动同步
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity); // 设置活动上下文
Intent intent = new Intent(activity, GTaskSyncService.class); // 创建启动服务的Intent
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 设置操作为开始同步
activity.startService(intent); // 启动服务
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
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); // 设置操作为取消同步
context.startService(intent); // 启动服务
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; // 根据同步任务是否存在判断是否正在同步
return mSyncTask != null;
}
// 静态方法:获取当前同步进度消息
public static String getProgressString() {
return mSyncProgress; // 返回同步进度消息
return mSyncProgress;
}
}
}

@ -15,7 +15,6 @@
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
@ -34,40 +33,27 @@ import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
/**
* Note
*
*/
public class Note {
// 笔记表的差异值(需要更新的字段)
private ContentValues mNoteDiffValues;
// 笔记数据对象,处理内容数据
private NoteData mNoteData;
// 日志标签
private static final String TAG = "Note";
/**
* ID
* @param context
* @param folderId ID
* @return ID0
* 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); // 父文件夹ID
// 插入数据库
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 {
// 从URI中解析出笔记ID
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
@ -79,106 +65,63 @@ public class Note {
return noteId;
}
/**
*
*/
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
/**
*
* @param key
* @param value
*/
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
// 设置本地修改标志和修改时间
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
*
* @param key
* @param value
*/
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
/**
* ID
* @param id ID
*/
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
/**
* ID
* @return ID
*/
public long getTextDataId() {
return mNoteData.mTextDataId;
}
/**
* ID
* @param id ID
*/
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}
/**
*
* @param key
* @param value
*/
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
/**
*
* @return
*/
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
*
* @param context
* @param noteId ID
* @return
*/
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 如果没有本地修改,直接返回成功
if (!isLocalModified()) {
return true;
}
/**
* {@link NoteColumns#LOCAL_MODIFIED}
* {@link NoteColumns#MODIFIED_DATE}使
*
* 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(); // 清空差异值
mNoteDiffValues.clear();
// 同步笔记数据
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
@ -187,43 +130,28 @@ public class Note {
return true;
}
/**
*
*/
private class NoteData {
// 文本数据ID
private long mTextDataId;
// 文本数据的差异值
private ContentValues mTextDataValues;
// 通话数据ID
private long mCallDataId;
// 通话数据的差异值
private ContentValues mCallDataValues;
// 日志标签
private static final String TAG = "NoteData";
/**
*
*/
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0; // 0表示新数据
mCallDataId = 0; // 0表示新数据
mTextDataId = 0;
mCallDataId = 0;
}
/**
*
* @return
*/
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
/**
* ID
* @param id ID
*/
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
@ -231,10 +159,6 @@ public class Note {
mTextDataId = id;
}
/**
* ID
* @param id ID
*/
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
@ -242,39 +166,21 @@ public class Note {
mCallDataId = id;
}
/**
*
* @param key
* @param value
*/
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
// 标记笔记为已修改
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
*
* @param key
* @param value
*/
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
// 标记笔记为已修改
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* ContentResolver
* @param context
* @param noteId ID
* @return URInull
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
*
* Check for safety
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
@ -283,16 +189,13 @@ public class Note {
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
// 处理文本数据
if(mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId); // 设置笔记ID
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 {
// 从URI中获取新插入的数据ID
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
@ -300,25 +203,21 @@ public class Note {
return null;
}
} else {
// 现有数据,执行更新操作
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
mTextDataValues.clear(); // 清空已处理的数据
mTextDataValues.clear();
}
// 处理通话数据
if(mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId); // 设置笔记ID
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 {
// 从URI中获取新插入的数据ID
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
@ -326,21 +225,18 @@ public class Note {
return null;
}
} else {
// 现有数据,执行更新操作
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear(); // 清空已处理的数据
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) {
@ -354,4 +250,4 @@ public class Note {
return null;
}
}
}
}

@ -31,42 +31,37 @@ 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对象用于处理数据层操作
// Note for the working note
private Note mNote;
// 笔记ID0表示新笔记
// Note Id
private long mNoteId;
// 笔记内容
// Note content
private String mContent;
// 笔记模式(如待办事项模式)
// Note mode
private int mMode;
// 提醒日期
private long mAlertDate;
// 修改日期
private long mModifiedDate;
// 背景颜色ID
private int mBgColorId;
// 关联的小部件ID
private int mWidgetId;
// 小部件类型
private int mWidgetType;
// 所属文件夹ID
private long mFolderId;
// 应用上下文
private 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,
@ -77,7 +72,6 @@ public class WorkingNote {
DataColumns.DATA4,
};
// 笔记表查询字段投影
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
@ -87,64 +81,56 @@ public class WorkingNote {
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;
/**
* -
* @param context
* @param folderId ID
*/
// New note construct
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0; // 初始无提醒
mModifiedDate = System.currentTimeMillis(); // 设置当前时间为修改时间
mAlertDate = 0;
mModifiedDate = System.currentTimeMillis();
mFolderId = folderId;
mNote = new Note(); // 创建新的Note对象
mNoteId = 0; // 新笔记ID为0
mNote = new Note();
mNoteId = 0;
mIsDeleted = false;
mMode = 0; // 默认模式
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 无效的小部件类型
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
/**
* -
* @param context
* @param noteId ID
* @param folderId ID
*/
// Existing note construct
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote(); // 从数据库加载笔记数据
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);
@ -157,14 +143,10 @@ public class WorkingNote {
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
loadNoteData(); // 加载笔记内容数据
loadNoteData();
}
/**
*
*/
private void loadNoteData() {
// 查询数据表获取笔记内容
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
@ -175,12 +157,10 @@ public class WorkingNote {
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);
@ -194,15 +174,6 @@ public class WorkingNote {
}
}
/**
*
* @param context
* @param folderId ID
* @param widgetId ID
* @param widgetType
* @param defaultBgColorId ID
* @return WorkingNote
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
@ -212,24 +183,12 @@ public class WorkingNote {
return note;
}
/**
*
* @param context
* @param id ID
* @return WorkingNote
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
/**
*
* @return
*/
public synchronized boolean saveNote() {
// 检查是否值得保存
if (isWorthSaving()) {
// 如果是新笔记,先创建数据库记录
if (!existInDatabase()) {
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
@ -237,10 +196,11 @@ public class WorkingNote {
}
}
// 同步数据到数据库
mNote.syncNote(mContext, mNoteId);
// 如果有关联的小部件,通知更新
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
@ -252,19 +212,10 @@ public class WorkingNote {
}
}
/**
*
* @return
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
*
*
* @return
*/
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
@ -274,19 +225,10 @@ public class WorkingNote {
}
}
/**
*
* @param l
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
/**
*
* @param date
* @param set
*/
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date;
@ -297,23 +239,14 @@ public class WorkingNote {
}
}
/**
*
* @param mark
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark;
// 如果有小部件,通知更新
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
/**
* ID
* @param id ID
*/
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
@ -324,10 +257,6 @@ public class WorkingNote {
}
}
/**
*
* @param mode
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
@ -338,10 +267,6 @@ public class WorkingNote {
}
}
/**
*
* @param type
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
@ -349,10 +274,6 @@ public class WorkingNote {
}
}
/**
* ID
* @param id ID
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
@ -360,10 +281,6 @@ public class WorkingNote {
}
}
/**
*
* @param text
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
@ -371,141 +288,81 @@ public class WorkingNote {
}
}
/**
*
* @param phoneNumber
* @param callDate
*/
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
/**
*
* @return
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
// 以下为获取各种属性的方法
/**
*
* @return
*/
public String getContent() {
return mContent;
}
/**
*
* @return
*/
public long getAlertDate() {
return mAlertDate;
}
/**
*
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* ID
* @return ID
*/
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
/**
* ID
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* ID
* @return ID
*/
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
/**
*
* @return
*/
public int getCheckListMode() {
return mMode;
}
/**
* ID
* @return ID
*/
public long getNoteId() {
return mNoteId;
}
/**
* ID
* @return ID
*/
public long getFolderId() {
return mFolderId;
}
/**
* ID
* @return ID
*/
public int getWidgetId() {
return mWidgetId;
}
/**
*
* @return
*/
public int getWidgetType() {
return mWidgetType;
}
/**
*
*/
public interface NoteSettingChangedListener {
/**
*
* Called when the background color of current note has just changed
*/
void onBackgroundColorChanged();
/**
*
* @param date
* @param set
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);
/**
*
* Call when user create note from widget
*/
void onWidgetChanged();
/**
*
* @param oldMode
* @param newMode
* 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,393 +1,344 @@
[file name]: BackupUtils.java
[file content begin]
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* 2010-2011MiCode
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Apache License 2.0
* 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;
// 导入Android相关类
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; // 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; // 笔记列定义
// 导入Java IO类
import java.io.File; // 文件类
import java.io.FileNotFoundException; // 文件未找到异常
import java.io.FileOutputStream; // 文件输出流
import java.io.IOException; // IO异常
import java.io.PrintStream; // 打印流
// 备份工具类:负责将笔记数据导出为文本文件
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils"; // 日志标签
// 单例模式相关
private static BackupUtils sInstance; // 静态单例实例
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
// 获取单例实例的静态方法使用synchronized确保线程安全
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) { // 如果实例为空
sInstance = new BackupUtils(context); // 创建新实例
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance; // 返回实例
return sInstance;
}
/**
*
* Following states are signs to represents backup or restore
* status
*/
// 状态常量定义
public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载状态
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在状态
public static final int STATE_DATA_DESTROIED = 2; // 数据被破坏状态
public static final int STATE_SYSTEM_ERROR = 3; // 系统错误状态
public static final int STATE_SUCCESS = 4; // 成功状态
private TextExport mTextExport; // 文本导出器实例
// 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); // 创建文本导出器
mTextExport = new TextExport(context);
}
// 检查外部存储是否可用的静态方法
private static boolean externalStorageAvailable() {
// 判断外部存储状态是否为已挂载
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 导出数据到文本文件的公共方法
public int exportToText() {
return mTextExport.exportToText(); // 调用文本导出器的导出方法
return mTextExport.exportToText();
}
// 获取导出的文本文件名
public String getExportedTextFileName() {
return mTextExport.mFileName; // 返回文件名
return mTextExport.mFileName;
}
// 获取导出的文本文件目录
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory; // 返回文件目录
return mTextExport.mFileDirectory;
}
// 内部类:文本导出器,实现具体的导出逻辑
private static class TextExport {
// 笔记表查询字段数组,定义需要查询的列
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID, // 笔记ID列
NoteColumns.MODIFIED_DATE, // 修改日期列
NoteColumns.SNIPPET, // 内容摘要列
NoteColumns.TYPE // 类型列
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
// 笔记列索引常量
private static final int NOTE_COLUMN_ID = 0; // ID列索引
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; // 修改日期列索引
private static final int NOTE_COLUMN_SNIPPET = 2; // 内容摘要列索引
private static final 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, // MIME类型列
DataColumns.DATA1, // 数据1列
DataColumns.DATA2, // 数据2列
DataColumns.DATA3, // 数据3列
DataColumns.DATA4, // 数据4列
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; // MIME类型列索引
private static final int DATA_COLUMN_CALL_DATE = 2; // 通话日期列索引
private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 电话号码列索引
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
// 文本格式化字符串数组,从资源文件中读取
private final String [] TEXT_FORMAT;
// 格式化索引常量
private static final int FORMAT_FOLDER_NAME = 0; // 文件夹名称格式索引
private static final int FORMAT_NOTE_DATE = 1; // 笔记日期格式索引
private static final int FORMAT_NOTE_CONTENT = 2; // 笔记内容格式索引
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
private Context mContext; // 上下文对象
private String mFileName; // 文件名
private String mFileDirectory; // 文件目录
private Context mContext;
private String mFileName;
private String mFileDirectory;
// 构造函数
public TextExport(Context context) {
// 从资源文件获取文本格式化数组
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context; // 保存上下文
mFileName = ""; // 初始化文件名为空
mFileDirectory = ""; // 初始化文件目录为空
mContext = context;
mFileName = "";
mFileDirectory = "";
}
// 获取指定索引的格式化字符串
private String getFormat(int id) {
return TEXT_FORMAT[id]; // 返回格式化字符串
return TEXT_FORMAT[id];
}
/**
*
* @param folderId ID
* @param ps
* 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 + "=?", // 查询条件父ID等于指定文件夹ID
new String[] { folderId }, // 查询参数
null); // 排序方式(无)
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) { // 如果游标不为空
if (notesCursor.moveToFirst()) { // 如果游标移动到第一行
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)))); // 修改日期
// 查询属于该笔记的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID); // 获取笔记ID
exportNoteToText(noteId, ps); // 导出该笔记的内容
} while (notesCursor.moveToNext()); // 移动到下一行
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(); // 关闭游标
notesCursor.close();
}
}
/**
*
* @param noteId ID
* @param ps
* 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 + "=?", // 查询条件笔记ID等于指定笔记ID
new String[] { noteId }, // 查询参数
null); // 排序方式(无)
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) { // 如果游标不为空
if (dataCursor.moveToFirst()) { // 如果游标移动到第一行
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
// 获取MIME类型
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) { // 如果是通话笔记类型
// 获取通话笔记的各个字段
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)) { // 如果电话号码不为空
// 打印电话号码
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)));
// 打印通话附件位置
if (!TextUtils.isEmpty(location)) { // 如果位置信息不为空
// 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)) { // 如果内容不为空
// 打印内容
} 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()); // 移动到下一行
} while (dataCursor.moveToNext());
}
dataCursor.close(); // 关闭游标
dataCursor.close();
}
// 在笔记之间打印分隔符
// print a line separator between note
try {
ps.write(new byte[] { // 写入字节数组
Character.LINE_SEPARATOR, // 行分隔符
Character.LETTER_NUMBER // 字母数字字符
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) { // 捕获IO异常
Log.e(TAG, e.toString()); // 记录错误日志
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
*
* @return
* 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; // 返回SD卡未挂载状态
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; // 返回系统错误状态
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 首先导出文件夹及其笔记
// 查询所有文件夹(排除垃圾箱)和通话记录文件夹
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, // 查询条件
null, // 查询参数
null); // 排序方式
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) { // 如果游标不为空
if (folderCursor.moveToFirst()) { // 如果游标移动到第一行
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// 打印文件夹名称
String folderName = ""; // 初始化文件夹名称为空
// 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)) { // 如果文件夹名称不为空
// 打印文件夹名称
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID); // 获取文件夹ID
exportFolderToText(folderId, ps); // 导出该文件夹下的笔记
} while (folderCursor.moveToNext()); // 移动到下一行
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close(); // 关闭游标
folderCursor.close();
}
// 导出根目录下的笔记父ID为0的笔记
// 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", // 查询条件类型为笔记且父ID为0
null, // 查询参数
null); // 排序方式
+ "=0", null, null);
if (noteCursor != null) { // 如果游标不为空
if (noteCursor.moveToFirst()) { // 如果游标移动到第一行
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
// 打印笔记的修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm), // 日期格式
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); // 修改日期
// 查询属于该笔记的数据
String noteId = noteCursor.getString(NOTE_COLUMN_ID); // 获取笔记ID
exportNoteToText(noteId, ps); // 导出该笔记的内容
} while (noteCursor.moveToNext()); // 移动到下一行
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(); // 关闭游标
noteCursor.close();
}
ps.close(); // 关闭打印流
ps.close();
return STATE_SUCCESS; // 返回成功状态
return STATE_SUCCESS;
}
/**
*
* @return null
* 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; // 返回null
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; // 初始化打印流
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; // 返回null
} catch (NullPointerException e) { // 捕获空指针异常
e.printStackTrace(); // 打印异常堆栈
return null; // 返回null
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; // 返回打印流
return ps;
}
}
/**
* SD
* @param context
* @param filePathResId ID
* @param fileNameFormatResId ID
* @return Filenull
* 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, // 文件名格式资源ID
DateFormat.format(context.getString(R.string.format_date_ymd), // 日期格式
System.currentTimeMillis()))); // 当前时间
File file = new File(sb.toString()); // 创建文件对象
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 (!filedir.exists()) {
filedir.mkdir();
}
if (!file.exists()) { // 如果文件不存在
file.createNewFile(); // 创建新文件
if (!file.exists()) {
file.createNewFile();
}
return file; // 返回文件对象
} catch (SecurityException e) { // 捕获安全异常
e.printStackTrace(); // 打印异常堆栈
} catch (IOException e) { // 捕获IO异常
e.printStackTrace(); // 打印异常堆栈
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null; // 如果失败返回null
return null;
}
}
[file content end]

@ -1,374 +1,504 @@
[file name]: DataUtils.java
[file content begin]
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* 2010-2011MiCode
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Apache License 2.0
* 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;
// 导入Android相关类
import android.content.ContentProviderOperation; // 内容提供器操作类
import android.content.ContentProviderResult; // 内容提供器结果类
import android.content.ContentResolver; // 内容解析器类
import android.content.ContentUris; // 内容URI工具类
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; // 小部件属性类
// 导入Java集合类
import java.util.ArrayList; // 动态数组类
import java.util.HashSet; // 哈希集合类
// 数据库工具类,提供对笔记数据的各种操作
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"; // 日志标签
// 批量删除笔记的方法
// 参数resolver - 内容解析器ids - 要删除的笔记ID集合
// 返回值boolean - 删除是否成功
public static final String TAG = "DataUtils";
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) { // 如果ID集合为空
Log.d(TAG, "the ids is null"); // 记录调试日志
return true; // 返回成功(无需删除)
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) { // 如果ID集合大小为0
Log.d(TAG, "no id is in the hashset"); // 记录调试日志
return true; // 返回成功(无需删除)
if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset");
return true;
}
// 创建内容提供器操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) { // 遍历ID集合
if(id == Notes.ID_ROOT_FOLDER) { // 如果是根文件夹ID
Log.e(TAG, "Don't delete system folder root"); // 记录错误日志
continue; // 跳过,不删除系统根文件夹
int deletedCount = 0;
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
// 创建删除操作
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build()); // 添加到操作列表
}
try {
// 批量执行操作
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
// 如果结果为空或无效
Log.d(TAG, "delete notes failed, ids:" + ids.toString()); // 记录调试日志
return false; // 返回失败
// 逐个删除,确保每个便签都被正确删除
int count = resolver.delete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), null, null);
if (count > 0) {
deletedCount++;
}
return true; // 返回成功
} catch (RemoteException e) { // 捕获远程异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录错误日志
} catch (OperationApplicationException e) { // 捕获操作应用异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录错误日志
}
return false; // 返回失败
return deletedCount > 0;
}
// 将笔记移动到其他文件夹的方法
// 参数resolver - 内容解析器id - 笔记IDsrcFolderId - 源文件夹IDdesFolderId - 目标文件夹ID
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues(); // 创建内容值对象
values.put(NoteColumns.PARENT_ID, desFolderId); // 设置父文件夹ID为目标文件夹ID
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); // 设置原始父文件夹ID
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 设置本地修改标志为1已修改
// 更新笔记
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);
}
// 批量移动笔记到指定文件夹的方法
// 参数resolver - 内容解析器ids - 笔记ID集合folderId - 目标文件夹ID
// 返回值boolean - 移动是否成功
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) { // 如果ID集合为空
Log.d(TAG, "the ids is null"); // 记录调试日志
return true; // 返回成功(无需移动)
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
// 创建内容提供器操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) { // 遍历ID集合
// 创建更新操作
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId); // 设置父文件夹ID
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 设置本地修改标志
operationList.add(builder.build()); // 添加到操作列表
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);
if (results == null || results.length == 0 || results[0] == null) {
// 如果结果为空或无效
Log.d(TAG, "delete notes failed, ids:" + ids.toString()); // 记录调试日志
return false; // 返回失败
}
return true; // 返回成功
} catch (RemoteException e) { // 捕获远程异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录错误日志
} catch (OperationApplicationException e) { // 捕获操作应用异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录错误日志
return 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; // 返回失败
return false;
}
/**
*
* resolver -
* int -
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) {
// 查询用户文件夹数量
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" }, // 查询计数
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", // 查询条件
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, // 参数
null); // 排序方式
int count = 0; // 初始化计数
if(cursor != null) { // 如果游标不为空
if(cursor.moveToFirst()) { // 如果游标移动到第一行
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()); // 记录错误日志
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close(); // 关闭游标
cursor.close();
}
}
}
return count; // 返回计数
return count;
}
// 检查指定类型的笔记是否在数据库中可见(不在垃圾箱中)
// 参数resolver - 内容解析器noteId - 笔记IDtype - 笔记类型
// 返回值boolean - 是否可见
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
// 查询指定ID和类型的笔记
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, // 所有列
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, // 条件
new String [] {String.valueOf(type)}, // 参数
null); // 排序
boolean exist = false; // 初始化存在标志
if (cursor != null) { // 如果游标不为空
if (cursor.getCount() > 0) { // 如果结果数大于0
exist = true; // 设置存在标志为true
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(); // 关闭游标
cursor.close();
}
return exist; // 返回存在标志
return exist;
}
// 检查笔记是否存在于笔记数据库中
// 参数resolver - 内容解析器noteId - 笔记ID
// 返回值boolean - 是否存在
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
// 查询指定ID的笔记
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) { // 如果结果数大于0
exist = true; // 设置存在标志为true
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close(); // 关闭游标
cursor.close();
}
return exist; // 返回存在标志
return exist;
}
// 检查数据是否存在于数据数据库中
// 参数resolver - 内容解析器dataId - 数据ID
// 返回值boolean - 是否存在
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
// 查询指定ID的数据
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) { // 如果结果数大于0
exist = true; // 设置存在标志为true
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close(); // 关闭游标
cursor.close();
}
return exist; // 返回存在标志
return exist;
}
// 检查可见文件夹名称是否已存在
// 参数resolver - 内容解析器name - 文件夹名称
// 返回值boolean - 是否存在
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
// 查询指定名称的文件夹
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + // 类型为文件夹
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + // 不在垃圾箱
" AND " + NoteColumns.SNIPPET + "=?", // 名称匹配
new String[] { name }, // 参数
null); // 排序
boolean exist = false; // 初始化存在标志
if(cursor != null) { // 如果游标不为空
if(cursor.getCount() > 0) { // 如果结果数大于0
exist = true; // 设置存在标志为true
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(); // 关闭游标
cursor.close();
}
return exist; // 返回存在标志
return exist;
}
// 获取文件夹中的笔记小部件属性
// 参数resolver - 内容解析器folderId - 文件夹ID
// 返回值HashSet<AppWidgetAttribute> - 小部件属性集合
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 }, // 查询小部件ID和类型
NoteColumns.PARENT_ID + "=?", // 父文件夹ID条件
new String[] { String.valueOf(folderId) }, // 参数
null); // 排序
HashSet<AppWidgetAttribute> set = null; // 初始化小部件属性集合
if (c != null) { // 如果游标不为空
if (c.moveToFirst()) { // 如果游标移动到第一行
set = new HashSet<AppWidgetAttribute>(); // 创建集合
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); // 获取小部件ID
widget.widgetType = c.getInt(1); // 获取小部件类型
set.add(widget); // 添加到集合
} catch (IndexOutOfBoundsException e) { // 捕获索引越界异常
Log.e(TAG, e.toString()); // 记录错误日志
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()); // 移动到下一行
} while (c.moveToNext());
}
c.close(); // 关闭游标
c.close();
}
return set; // 返回集合
return set;
}
// 根据笔记ID获取通话号码
// 参数resolver - 内容解析器noteId - 笔记ID
// 返回值String - 电话号码,如果不存在返回空字符串
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); // 排序
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()) { // 如果游标不为空且移动到第一行
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0); // 返回电话号码
} catch (IndexOutOfBoundsException e) { // 捕获索引越界异常
Log.e(TAG, "Get call number fails " + e.toString()); // 记录错误日志
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
cursor.close(); // 关闭游标
cursor.close();
}
}
return ""; // 返回空字符串
return "";
}
// 根据电话号码和通话日期获取笔记ID
// 参数resolver - 内容解析器phoneNumber - 电话号码callDate - 通话日期
// 返回值long - 笔记ID如果不存在返回0
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
// 查询通话笔记的笔记ID
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID }, // 查询笔记ID列
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); // 排序
+ CallNote.PHONE_NUMBER + ",?)",
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null);
if (cursor != null) { // 如果游标不为空
if (cursor.moveToFirst()) { // 如果游标移动到第一行
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0); // 返回笔记ID
} catch (IndexOutOfBoundsException e) { // 捕获索引越界异常
Log.e(TAG, "Get call note id fails " + e.toString()); // 记录错误日志
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
cursor.close(); // 关闭游标
cursor.close();
}
return 0; // 返回0表示不存在
return 0;
}
// 根据笔记ID获取内容摘要
// 参数resolver - 内容解析器noteId - 笔记ID
// 返回值String - 内容摘要,如果不存在抛出异常
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); // 获取内容摘要
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; // 返回内容摘要
cursor.close();
return snippet;
}
throw new IllegalArgumentException("Note is not found with id: " + noteId); // 抛出异常
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
// 格式化内容摘要(去除换行符和多余空格)
// 参数snippet - 原始内容摘要
// 返回值String - 格式化后的内容摘要
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); // 截取到换行符之前的内容
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 snippet; // 返回格式化后的内容摘要
return deletedCount;
}
}
[file content end]

@ -1,109 +1,113 @@
[file name]: GTaskStringUtils.java
[file content begin]
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* 2010-2011MiCode
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Apache License 2.0
* 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;
// Google Task相关JSON字段和常量的工具类
public class GTaskStringUtils {
// JSON字段名常量定义
// 动作相关字段
public final static String GTASK_JSON_ACTION_ID = "action_id"; // 动作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"; // 创建者ID字段
// 实体相关字段
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; // 子实体字段
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_TYPE_GROUP = "GROUP"; // 组类型
public final static String GTASK_JSON_TYPE_TASK = "TASK"; // 任务类型
// 客户端版本字段
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"; // 当前列表ID字段
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; // 默认列表ID字段
public final static String GTASK_JSON_DEST_LIST = "dest_list"; // 目标列表字段
public final static String GTASK_JSON_LIST_ID = "list_id"; // 列表ID字段
public final static String GTASK_JSON_LISTS = "lists"; // 列表集合字段
public final static String GTASK_JSON_SOURCE_LIST = "source_list"; // 源列表字段
// 删除相关字段
public final static String GTASK_JSON_DELETED = "deleted"; // 删除状态字段
public final static String GTASK_JSON_GET_DELETED = "get_deleted"; // 获取删除项字段
// 父级相关字段
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_PARENT_ID = "parent_id"; // 父级ID字段
// 通用字段
public final static String GTASK_JSON_ID = "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_NAME = "name"; // 名称字段
public final static String GTASK_JSON_NEW_ID = "new_id"; // 新ID字段
public final static String GTASK_JSON_NOTES = "notes"; // 笔记字段
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; // 前兄弟ID字段
public final static String GTASK_JSON_RESULTS = "results"; // 结果字段
public final static String GTASK_JSON_TASKS = "tasks"; // 任务字段
public final static String GTASK_JSON_TYPE = "type"; // 类型字段
public final static String GTASK_JSON_USER = "user"; // 用户字段
// MIUI文件夹前缀
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; // MIUI笔记文件夹前缀
// 文件夹名称常量
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"; // 元数据GTask ID头部
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"; // 元数据笔记名称
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";
}
[file content end]

@ -1,229 +1,181 @@
[file name]: ResourceParser.java
[file content begin]
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* 2010-2011MiCode
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Apache License 2.0
* 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;
// 导入Android相关类
import android.content.Context; // 上下文类
import android.preference.PreferenceManager; // 偏好设置管理器
import android.content.Context;
import android.preference.PreferenceManager;
// 导入应用内部资源
import net.micode.notes.R; // R资源文件
import net.micode.notes.ui.NotesPreferenceActivity; // 笔记偏好设置活动
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 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 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 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 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, // 黄色编辑背景资源ID
R.drawable.edit_blue, // 蓝色编辑背景资源ID
R.drawable.edit_white, // 白色编辑背景资源ID
R.drawable.edit_green, // 绿色编辑背景资源ID
R.drawable.edit_red // 红色编辑背景资源ID
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, // 黄色标题背景资源ID
R.drawable.edit_title_blue, // 蓝色标题背景资源ID
R.drawable.edit_title_white, // 白色标题背景资源ID
R.drawable.edit_title_green, // 绿色标题背景资源ID
R.drawable.edit_title_red // 红色标题背景资源ID
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
// 获取笔记背景资源ID的方法
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id]; // 返回指定ID的背景资源
return BG_EDIT_RESOURCES[id];
}
// 获取笔记标题背景资源ID的方法
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id]; // 返回指定ID的标题背景资源
return BG_EDIT_TITLE_RESOURCES[id];
}
}
// 获取默认背景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 // 红色顶部背景
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 // 红色中间背景
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, // 红色底部背景
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 // 红色单个背景
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]; // 返回指定ID的第一个列表项背景资源
return BG_FIRST_RESOURCES[id];
}
// 获取最后一个列表项背景资源的方法
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id]; // 返回指定ID的最后一个列表项背景资源
return BG_LAST_RESOURCES[id];
}
// 获取单个列表项背景资源的方法
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id]; // 返回指定ID的单个列表项背景资源
return BG_SINGLE_RESOURCES[id];
}
// 获取中间列表项背景资源的方法
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id]; // 返回指定ID的中间列表项背景资源
return BG_NORMAL_RESOURCES[id];
}
// 获取文件夹背景资源的方法
public static int getFolderBgRes() {
return R.drawable.list_folder; // 返回文件夹背景资源ID
return R.drawable.list_folder;
}
}
// 小部件背景资源类:处理桌面小部件的背景资源
public static class WidgetBgResources {
// 2x小部件背景资源数组
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow, // 黄色2x小部件背景
R.drawable.widget_2x_blue, // 蓝色2x小部件背景
R.drawable.widget_2x_white, // 白色2x小部件背景
R.drawable.widget_2x_green, // 绿色2x小部件背景
R.drawable.widget_2x_red, // 红色2x小部件背景
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,
};
// 获取2x小部件背景资源的方法
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id]; // 返回指定ID的2x小部件背景资源
return BG_2X_RESOURCES[id];
}
// 4x小部件背景资源数组
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow, // 黄色4x小部件背景
R.drawable.widget_4x_blue, // 蓝色4x小部件背景
R.drawable.widget_4x_white, // 白色4x小部件背景
R.drawable.widget_4x_green, // 绿色4x小部件背景
R.drawable.widget_4x_red // 红色4x小部件背景
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
};
// 获取4x小部件背景资源的方法
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id]; // 返回指定ID的4x小部件背景资源
return BG_4X_RESOURCES[id];
}
}
// 文本外观资源类:处理文本样式资源
public static class TextAppearanceResources {
// 文本外观资源数组,对应不同的字体大小样式
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal, // 正常文本外观
R.style.TextAppearanceMedium, // 中等文本外观
R.style.TextAppearanceLarge, // 大文本外观
R.style.TextAppearanceSuper // 超大文本外观
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
// 获取文本外观资源的方法
public static int getTexAppearanceResource(int id) {
/**
* HACKME: SharedPreferenceIDbug
* 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) {
// 如果ID超出范围返回默认字体大小
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id]; // 返回指定ID的文本外观资源
return TEXTAPPEARANCE_RESOURCES[id];
}
// 获取资源数组大小的方法
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length; // 返回文本外观资源数组的长度
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}
[file content end]

@ -61,5 +61,28 @@ public class AlarmInitReceiver extends BroadcastReceiver {
}
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);
}
}

@ -19,14 +19,19 @@ package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -46,6 +51,7 @@ import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
@ -54,14 +60,17 @@ 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.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.PasswordUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.ui.PasswordSettingDialog;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
@ -144,11 +153,18 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
// 密码菜单项ID
private static final int MENU_SET_PASSWORD = 1001;
private static final int MENU_MODIFY_PASSWORD = 1002;
private static final int MENU_DELETE_PASSWORD = 1003;
private LinearLayout mEditTextList;
private String mUserQuery;
private Pattern mPattern;
private TextView mWordCountTextView; // 字数统计TextView
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -293,6 +309,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* is not ready
*/
showAlertHeader();
// 更新字数统计
updateWordCount();
}
private void showAlertHeader() {
@ -395,6 +413,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
// 初始化字数统计TextView
mWordCountTextView = (TextView) findViewById(R.id.tv_word_count);
// 为普通模式的编辑框添加一次TextWatcher避免重复添加
mNoteEditor.addTextChangedListener(new WordCountTextWatcher());
updateWordCount(); // 设置初始字数
}
@Override
@ -430,7 +454,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
@ -502,6 +526,32 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
// 根据当前便签的置顶状态显示或隐藏置顶/取消置顶菜单项
Cursor cursor = getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()),
new String[] { NoteColumns.STICKY },
null, null, null);
boolean isSticky = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
isSticky = cursor.getInt(0) == 1;
}
cursor.close();
}
menu.findItem(R.id.menu_sticky).setVisible(!isSticky);
menu.findItem(R.id.menu_unsticky).setVisible(isSticky);
// 添加设置密码菜单项
if (PasswordUtils.isNotePasswordSet(getContentResolver(), mWorkingNote.getNoteId())) {
menu.add(0, MENU_MODIFY_PASSWORD, 0, "修改密码");
menu.add(0, MENU_DELETE_PASSWORD, 0, "删除密码");
} else {
menu.add(0, MENU_SET_PASSWORD, 0, "设置密码");
}
return true;
}
@ -547,6 +597,30 @@ public class NoteEditActivity extends Activity implements OnClickListener,
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
case R.id.menu_sticky:
// 置顶当前便签
setNoteSticky(true);
break;
case R.id.menu_unsticky:
// 取消置顶当前便签
setNoteSticky(false);
break;
case R.id.menu_rename:
// 重命名当前便签
showRenameDialog();
break;
case MENU_SET_PASSWORD:
// 打开密码设置对话框
showPasswordSettingDialog(mWorkingNote.getNoteId());
break;
case MENU_MODIFY_PASSWORD:
// 先验证当前密码
verifyCurrentPasswordBeforeModify();
break;
case MENU_DELETE_PASSWORD:
// 打开删除密码确认对话框
showDeletePasswordDialog(mWorkingNote.getNoteId());
break;
default:
break;
}
@ -595,14 +669,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
// 无论是否同步,都将便签移动到回收站
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
mWorkingNote.markDeleted(true);
@ -753,6 +822,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
// 添加TextWatcher实现实时字数统计
edit.addTextChangedListener(new WordCountTextWatcher());
return view;
}
@ -780,6 +851,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
// 切换模式后更新字数统计
updateWordCount();
}
private boolean getWorkingText() {
@ -870,4 +943,246 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
/**
* 便
* @param sticky
*/
private void setNoteSticky(boolean sticky) {
ContentValues values = new ContentValues();
values.put(Notes.NoteColumns.STICKY, sticky ? 1 : 0);
// 更新便签的置顶状态
getContentResolver().update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()),
values, null, null);
// 更新标题栏显示
initNoteScreen();
}
/**
*
*/
private void showRenameDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
// 显示当前便签的标题snippet作为默认名称
String currentTitle = mWorkingNote.getContent();
etName.setText(currentTitle);
builder.setTitle("重命名便签");
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String newName = etName.getText().toString();
if (!TextUtils.isEmpty(newName)) {
// 只更新便签的标题SNIPPET字段不修改内容
ContentValues values = new ContentValues();
values.put(Notes.NoteColumns.SNIPPET, newName);
values.put(Notes.NoteColumns.LOCAL_MODIFIED, 1);
values.put(Notes.NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
// 执行更新操作
getContentResolver().update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()),
values, null, null);
// 刷新UI显示
initNoteScreen();
}
dialog.dismiss();
}
});
}
/**
*
* @param noteId ID
*/
private void showPasswordSettingDialog(long noteId) {
PasswordSettingDialog dialog = new PasswordSettingDialog(this, noteId, new PasswordSettingDialog.OnPasswordSetListener() {
@Override
public void onPasswordSet(boolean success) {
if (success) {
Toast.makeText(NoteEditActivity.this, "密码设置成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NoteEditActivity.this, "密码设置失败", Toast.LENGTH_SHORT).show();
}
}
});
dialog.show();
}
private void verifyCurrentPasswordBeforeModify() {
final long noteId = mWorkingNote.getNoteId();
// 获取笔记的密码类型
int passwordType = PasswordUtils.getNotePasswordType(getContentResolver(), noteId);
if (passwordType == Notes.PASSWORD_TYPE_DIGIT) {
// 显示数字密码验证对话框
DigitPasswordDialog dialog = new DigitPasswordDialog(this);
dialog.setOnPasswordVerifyListener(new DigitPasswordDialog.OnPasswordVerifyListener() {
@Override
public void onPasswordVerify(String password) {
if (password != null) {
// 验证密码是否正确
if (PasswordUtils.verifyNotePassword(getContentResolver(), noteId, password)) {
// 密码正确,显示密码设置对话框
showPasswordSettingDialog(noteId);
} else {
// 密码错误,提示用户
Toast.makeText(NoteEditActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
}
}
}
});
dialog.show();
} else if (passwordType == Notes.PASSWORD_TYPE_PATTERN) {
// 显示图案密码验证对话框
PatternPasswordDialog dialog = new PatternPasswordDialog(this);
dialog.setOnPasswordVerifyListener(new PatternPasswordDialog.OnPasswordVerifyListener() {
@Override
public void onPasswordVerify(String pattern) {
if (pattern != null) {
// 验证密码是否正确
if (PasswordUtils.verifyNotePassword(getContentResolver(), noteId, pattern)) {
// 密码正确,显示密码设置对话框
showPasswordSettingDialog(noteId);
} else {
// 密码错误,提示用户
Toast.makeText(NoteEditActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
}
}
}
});
dialog.show();
} else {
// 未设置密码,直接显示密码设置对话框
showPasswordSettingDialog(noteId);
}
}
private void showDeletePasswordDialog(long noteId) {
// 获取笔记的密码类型
int passwordType = PasswordUtils.getNotePasswordType(getContentResolver(), noteId);
if (passwordType == Notes.PASSWORD_TYPE_DIGIT) {
// 显示数字密码验证对话框
DigitPasswordDialog dialog = new DigitPasswordDialog(this);
dialog.setOnPasswordVerifyListener(new DigitPasswordDialog.OnPasswordVerifyListener() {
@Override
public void onPasswordVerify(String password) {
if (password != null) {
// 验证密码是否正确
if (PasswordUtils.verifyNotePassword(getContentResolver(), noteId, password)) {
// 密码正确,删除密码
boolean success = PasswordUtils.deleteNotePassword(getContentResolver(), noteId);
if (success) {
Toast.makeText(NoteEditActivity.this, "密码删除成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NoteEditActivity.this, "密码删除失败", Toast.LENGTH_SHORT).show();
}
} else {
// 密码错误,提示用户
Toast.makeText(NoteEditActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
}
}
}
});
dialog.show();
} else if (passwordType == Notes.PASSWORD_TYPE_PATTERN) {
// 显示图案密码验证对话框
PatternPasswordDialog dialog = new PatternPasswordDialog(this);
dialog.setOnPasswordVerifyListener(new PatternPasswordDialog.OnPasswordVerifyListener() {
@Override
public void onPasswordVerify(String pattern) {
if (pattern != null) {
// 验证密码是否正确
if (PasswordUtils.verifyNotePassword(getContentResolver(), noteId, pattern)) {
// 密码正确,删除密码
boolean success = PasswordUtils.deleteNotePassword(getContentResolver(), noteId);
if (success) {
Toast.makeText(NoteEditActivity.this, "密码删除成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NoteEditActivity.this, "密码删除失败", Toast.LENGTH_SHORT).show();
}
} else {
// 密码错误,提示用户
Toast.makeText(NoteEditActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
}
}
}
});
dialog.show();
}
}
/**
* 便
*/
private void updateNoteEditorContent() {
// 更新编辑器内容
mNoteEditor.setText(mWorkingNote.getContent());
}
/**
*
*/
private void updateWordCount() {
if (mWordCountTextView == null) {
return;
}
String content = getCurrentContent();
int wordCount = DataUtils.countWords(content);
mWordCountTextView.setText("字数:" + wordCount);
}
/**
*
* @return
*/
private String getCurrentContent() {
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
// List 模式下,获取所有列表项的内容
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View view = mEditTextList.getChildAt(i);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {
sb.append(edit.getText().toString()).append("\n");
}
}
return sb.toString();
} else {
// 普通模式下,获取编辑框的内容
return mNoteEditor.getText().toString();
}
}
/**
* TextWatcher
*/
private class WordCountTextWatcher implements android.text.TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 不需要处理
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 不需要处理
}
@Override
public void afterTextChanged(android.text.Editable s) {
// 文本变化后更新字数统计
updateWordCount();
}
}
}

@ -40,6 +40,7 @@ public class NoteItemData {
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.STICKY,
};
private static final int ID_COLUMN = 0;
@ -54,6 +55,7 @@ public class NoteItemData {
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;
@ -69,6 +71,7 @@ public class NoteItemData {
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mSticky;
private boolean mIsLastItem;
private boolean mIsFirstItem;
@ -91,6 +94,7 @@ public class NoteItemData {
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) {
@ -217,6 +221,10 @@ public class NoteItemData {
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);

@ -22,6 +22,7 @@ import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
@ -49,12 +50,15 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
@ -67,6 +71,7 @@ import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.PasswordUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
@ -88,6 +93,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
private static final int MENU_NOTE_RENAME = 3;
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
@ -119,6 +126,24 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private ModeCallback mModeCallBack;
// Search related variables
private boolean mIsSearchMode;
private LinearLayout mSearchBar;
private EditText mSearchEditText;
private ImageView mSearchIcon;
private ImageView mClearSearchBtn;
private TextView mCancelSearchBtn;
private String mSearchKeyword;
private static final int SEARCH_QUERY_TOKEN = 2;
private static final long SEARCH_DEBOUNCE_DELAY = 300; // 300ms debounce
private Runnable mSearchRunnable;
// Search suggestion popup related variables
private LinearLayout mSearchSuggestionPopup;
private ListView mSearchSuggestionListView;
private SearchSuggestionAdapter mSearchSuggestionAdapter;
private static final int SEARCH_SUGGESTION_QUERY_TOKEN = 3;
private static final String TAG = "NotesListActivity";
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
@ -139,6 +164,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list);
setTitle("Notes");
initResources();
/**
@ -223,12 +249,203 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
mAddNewNote.setOnClickListener(this);
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
mAddNewNote.setVisibility(View.VISIBLE);
// 初始化底部菜单按钮
Button menuButton = (Button) findViewById(R.id.menu_button);
if (menuButton != null) {
menuButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
openOptionsMenu();
}
});
}
mDispatch = false;
mDispatchY = 0;
mOriginY = 0;
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mState = ListEditState.NOTE_LIST;
mModeCallBack = new ModeCallback();
// Initialize search components
initSearchComponents();
}
/**
* Initialize search components and set up listeners
*/
private void initSearchComponents() {
mSearchBar = (LinearLayout) findViewById(R.id.search_bar);
mSearchEditText = (EditText) findViewById(R.id.search_edit_text);
mSearchIcon = (ImageView) findViewById(R.id.search_icon);
mClearSearchBtn = (ImageView) findViewById(R.id.clear_search);
mCancelSearchBtn = (TextView) findViewById(R.id.cancel_search);
// Set up search icon click listener
mSearchIcon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
activateSearchMode();
}
});
// Set up clear search button click listener
mClearSearchBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
clearSearch();
hideSearchSuggestionPopup();
}
});
// Set up cancel search button click listener
mCancelSearchBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
deactivateSearchMode();
hideSearchSuggestionPopup();
}
});
// Set up search text change listener
mSearchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Show/hide clear button based on text length
mClearSearchBtn.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE);
// Update search keyword
mSearchKeyword = s.toString().trim();
// Debounce search to avoid frequent queries
if (mSearchRunnable != null) {
mNotesListView.removeCallbacks(mSearchRunnable);
}
mSearchRunnable = new Runnable() {
@Override
public void run() {
startAsyncNotesListQuery(mSearchKeyword);
startAsyncSearchSuggestionQuery(mSearchKeyword);
}
};
mNotesListView.postDelayed(mSearchRunnable, SEARCH_DEBOUNCE_DELAY);
}
@Override
public void afterTextChanged(Editable s) {
// Do nothing
}
});
// Initialize search state
mIsSearchMode = false;
mSearchKeyword = "";
// Initialize search suggestion popup
initSearchSuggestionPopup();
}
/**
* Initialize search suggestion popup components
*/
private void initSearchSuggestionPopup() {
// Inflate search suggestion popup layout
mSearchSuggestionPopup = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.search_suggestion_popup, null);
mSearchSuggestionListView = (ListView) mSearchSuggestionPopup.findViewById(R.id.search_suggestion_list);
// Create search suggestion adapter and set it to the list view
mSearchSuggestionAdapter = new SearchSuggestionAdapter(this);
mSearchSuggestionListView.setAdapter(mSearchSuggestionAdapter);
// Set up search suggestion item click listener
mSearchSuggestionListView.setOnItemClickListener(new OnSearchSuggestionItemClickListener());
// Add the search suggestion popup to the parent of the search bar
// This ensures it appears immediately below the search bar
ViewGroup searchBarParent = (ViewGroup) mSearchBar.getParent();
// Find the index of the search bar in its parent
int searchBarIndex = -1;
for (int i = 0; i < searchBarParent.getChildCount(); i++) {
if (searchBarParent.getChildAt(i) == mSearchBar) {
searchBarIndex = i;
break;
}
}
// Add the search suggestion popup right after the search bar
if (searchBarIndex != -1) {
searchBarParent.addView(mSearchSuggestionPopup, searchBarIndex + 1);
} else {
// If search bar not found, add to the end
searchBarParent.addView(mSearchSuggestionPopup);
}
// Hide the popup initially
hideSearchSuggestionPopup();
}
/**
* Show search suggestion popup
*/
private void showSearchSuggestionPopup() {
if (mSearchSuggestionPopup != null) {
mSearchSuggestionPopup.setVisibility(View.VISIBLE);
}
}
/**
* Hide search suggestion popup
*/
private void hideSearchSuggestionPopup() {
if (mSearchSuggestionPopup != null) {
mSearchSuggestionPopup.setVisibility(View.GONE);
}
}
/**
* Activate search mode
*/
private void activateSearchMode() {
mIsSearchMode = true;
mSearchBar.setVisibility(View.VISIBLE);
mTitleBar.setVisibility(View.GONE);
mSearchEditText.requestFocus();
showSoftInput();
}
/**
* Deactivate search mode
*/
private void deactivateSearchMode() {
mIsSearchMode = false;
mSearchBar.setVisibility(View.GONE);
if (mState == ListEditState.NOTE_LIST) {
mTitleBar.setVisibility(View.GONE);
} else {
mTitleBar.setVisibility(View.VISIBLE);
}
clearSearch();
hideSoftInput(mSearchEditText);
// Reset to normal list query
startAsyncNotesListQuery(null);
}
/**
* Clear search content
*/
private void clearSearch() {
mSearchEditText.setText("");
mSearchKeyword = "";
mClearSearchBtn.setVisibility(View.GONE);
}
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
@ -238,10 +455,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu);
// 为所有菜单项设置点击监听器
menu.findItem(R.id.sticky).setOnMenuItemClickListener(this);
menu.findItem(R.id.unsticky).setOnMenuItemClickListener(this);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
if (mFocusNoteDataItem != null && (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0)) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
@ -287,8 +507,39 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
// 根据选中项的当前置顶状态显示或隐藏置顶/取消置顶菜单项
// 检查是否所有选中项都已置顶
boolean allSticky = true;
boolean anySticky = false;
// 查询所有选中项的置顶状态
for (Long id : mNotesListAdapter.getSelectedItemIds()) {
Cursor cursor = mContentResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
new String[] { NoteColumns.STICKY },
null, null, null);
boolean isSticky = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
isSticky = cursor.getInt(0) == 1;
}
cursor.close();
}
if (!isSticky) {
allSticky = false;
}
if (isSticky) {
anySticky = true;
}
}
// 显示或隐藏菜单项
menu.findItem(R.id.sticky).setVisible(!allSticky);
menu.findItem(R.id.unsticky).setVisible(anySticky);
return true;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
@ -320,6 +571,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
switch (item.getItemId()) {
case R.id.sticky:
// 置顶选中的笔记
batchSetSticky(true);
break;
case R.id.unsticky:
// 取消置顶选中的笔记
batchSetSticky(false);
break;
case R.id.delete:
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
@ -409,12 +668,66 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
};
private void startAsyncNotesListQuery() {
startAsyncNotesListQuery(null);
}
/**
* Start async notes list query with optional search keyword
* @param keyword Search keyword, null or empty for all notes
*/
private void startAsyncNotesListQuery(String keyword) {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
String[] selectionArgs;
// Add search condition if keyword is provided
if (!TextUtils.isEmpty(keyword)) {
// Combine folder selection with search condition
selection = selection + " AND " + NoteColumns.SNIPPET + " LIKE ?";
if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) {
selectionArgs = new String[] {
String.valueOf(Notes.ID_ROOT_FOLDER),
"%" + keyword + "%"
};
} else {
selectionArgs = new String[] {
String.valueOf(mCurrentFolderId),
"%" + keyword + "%"
};
}
} else {
selectionArgs = new String[] {
String.valueOf(mCurrentFolderId)
};
}
// 排序规则:置顶的笔记在最前面,然后是文件夹,最后是普通笔记,同类型内按修改时间倒序
String sortOrder = NoteColumns.STICKY + " DESC, " + NoteColumns.TYPE + " DESC, " + NoteColumns.MODIFIED_DATE + " DESC";
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs, sortOrder);
}
/**
* Start async search suggestion query with search keyword
* @param keyword Search keyword
*/
private void startAsyncSearchSuggestionQuery(String keyword) {
if (TextUtils.isEmpty(keyword)) {
// If keyword is empty, clear search suggestions and hide popup
mSearchSuggestionAdapter.changeCursor(null);
hideSearchSuggestionPopup();
return;
}
// Query for folders and notes that match the keyword
String selection = NoteColumns.SNIPPET + " LIKE ?";
String[] selectionArgs = new String[] { "%" + keyword + "%" };
// Sort order: folders first, then notes, by name ascending
String sortOrder = NoteColumns.TYPE + " DESC, " + NoteColumns.SNIPPET + " ASC";
mBackgroundQueryHandler.startQuery(SEARCH_SUGGESTION_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs, sortOrder);
}
private final class BackgroundQueryHandler extends AsyncQueryHandler {
@ -435,6 +748,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
Log.e(TAG, "Query folder failed");
}
break;
case SEARCH_SUGGESTION_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
// Update search suggestion adapter with new cursor
mSearchSuggestionAdapter.changeCursor(cursor);
// Show search suggestion popup
showSearchSuggestionPopup();
} else {
// No suggestions found, clear adapter and hide popup
mSearchSuggestionAdapter.changeCursor(null);
hideSearchSuggestionPopup();
}
break;
default:
return;
}
@ -473,20 +798,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
if (!isSyncMode()) {
// if not synced, delete notes directly
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// in sync mode, we'll move the deleted note into the trash
// folder
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
// 无论是否同步,都将便签移动到回收站
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
return widgets;
}
@ -505,6 +820,43 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}.execute();
}
/**
*
* @param sticky
*/
private void batchSetSticky(final boolean sticky) {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
ContentValues values = new ContentValues();
values.put(Notes.NoteColumns.STICKY, sticky ? 1 : 0);
// 批量更新置顶状态
for (Long id : mNotesListAdapter.getSelectedItemIds()) {
getContentResolver().update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
values, null, null);
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
// 刷新列表
startAsyncNotesListQuery();
mModeCallBack.finishActionMode();
}
}.execute();
}
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
@ -516,13 +868,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
ids.add(folderId);
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
if (!isSyncMode()) {
// if not synced, delete folder directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// in sync mode, we'll move the deleted folder into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
// 无论是否同步,都将文件夹移动到回收站
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLDER);
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
@ -534,13 +881,83 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
private void openNode(NoteItemData data) {
final long noteId = data.getId();
// 检查笔记是否设置了密码
if (PasswordUtils.isNotePasswordSet(mContentResolver, noteId)) {
// 获取密码类型
int passwordType = PasswordUtils.getNotePasswordType(mContentResolver, noteId);
// 根据密码类型显示相应的验证对话框
if (passwordType == Notes.PASSWORD_TYPE_DIGIT) {
// 显示数字密码验证对话框
DigitPasswordDialog dialog = new DigitPasswordDialog(this);
dialog.setOnPasswordVerifyListener(new DigitPasswordDialog.OnPasswordVerifyListener() {
@Override
public void onPasswordVerify(String password) {
if (password != null) {
// 验证密码
if (PasswordUtils.verifyNotePassword(mContentResolver, noteId, password)) {
// 密码正确,打开笔记
doOpenNode(noteId);
} else {
// 密码错误,提示用户
Toast.makeText(NotesListActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
}
}
}
});
dialog.show();
} else if (passwordType == Notes.PASSWORD_TYPE_PATTERN) {
// 显示九宫格图案密码验证对话框
PatternPasswordDialog dialog = new PatternPasswordDialog(this);
dialog.setOnPasswordVerifyListener(new PatternPasswordDialog.OnPasswordVerifyListener() {
@Override
public void onPasswordVerify(String pattern) {
if (pattern != null) {
// 验证密码
if (PasswordUtils.verifyNotePassword(mContentResolver, noteId, pattern)) {
// 密码正确,打开笔记
doOpenNode(noteId);
} else {
// 密码错误,提示用户
Toast.makeText(NotesListActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
}
}
}
});
dialog.show();
} else {
// 无密码,直接打开
doOpenNode(noteId);
}
} else {
// 未设置密码,直接打开
doOpenNode(noteId);
}
}
/**
*
* @param noteId ID
*/
private void doOpenNode(long noteId) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
intent.putExtra(Intent.EXTRA_UID, noteId);
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
private void openFolder(NoteItemData data) {
// 直接打开文件夹,不再进行文件夹级别的密码验证
doOpenFolder(data);
}
/**
*
* @param data
*/
private void doOpenFolder(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery();
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
@ -584,10 +1001,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
showSoftInput();
// Check if it's a folder or note
boolean isFolder = mFocusNoteDataItem != null && mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER;
if (!create) {
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet());
builder.setTitle(getString(R.string.menu_folder_change_name));
// Set different title based on type
if (isFolder) {
builder.setTitle(getString(R.string.menu_folder_change_name));
} else {
builder.setTitle("重命名便签");
}
} else {
Log.e(TAG, "The long click data item is null");
return;
@ -610,18 +1036,26 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public void onClick(View v) {
hideSoftInput(etName);
String name = etName.getText().toString();
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
// Only check folder name existence for folders
if (isFolder && DataUtils.checkVisibleFolderName(mContentResolver, name)) {
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show();
etName.setSelection(0, etName.length());
return;
}
if (!create) {
if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// Only set type for folders, not for notes
if (isFolder) {
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
}
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] {
String.valueOf(mFocusNoteDataItem.getId())
@ -707,6 +1141,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
setResult(RESULT_OK, intent);
}
private static final int MENU_FOLDER_SET_PASSWORD = 4;
private static final int MENU_FOLDER_MODIFY_PASSWORD = 5;
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
@ -753,6 +1190,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
case MENU_FOLDER_CHANGE_NAME:
showCreateOrModifyFolderDialog(false);
break;
case MENU_NOTE_RENAME:
// 使用现有的对话框实现便签重命名功能
showCreateOrModifyFolderDialog(false);
break;
default:
break;
}
@ -760,6 +1201,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.note_list, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
@ -809,8 +1256,32 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
createNewNote();
break;
}
case R.id.menu_rename_folder: {
// 处理文件夹重命名
// 查询当前文件夹的信息
Cursor cursor = mContentResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mCurrentFolderId),
NoteItemData.PROJECTION,
null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
// 创建当前文件夹的NoteItemData
mFocusNoteDataItem = new NoteItemData(this, cursor);
// 显示重命名对话框
showCreateOrModifyFolderDialog(false);
}
cursor.close();
}
break;
}
case R.id.menu_search:
onSearchRequested();
activateSearchMode();
break;
case R.id.menu_trash:
// 跳转到回收站页面
Intent intent = new Intent(this, TrashActivity.class);
startActivity(intent);
break;
default:
break;
@ -871,9 +1342,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1);
Intent intent = new Intent(this, NotesPreferenceActivity.class);
startActivityIfNeeded(intent, -1);
}
private class OnListItemClickListener implements OnItemClickListener {
@ -916,6 +1386,42 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
*/
private class SearchSuggestionAdapter extends NotesListAdapter {
public SearchSuggestionAdapter(Context context) {
super(context);
}
// 注意CursorAdapter使用newView和bindView方法而不是getView方法
// 如果需要自定义搜索建议项的样式,可以重写这两个方法
}
/**
*
*/
private class OnSearchSuggestionItemClickListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
// 隐藏搜索建议弹窗
hideSearchSuggestionPopup();
// 根据点击的项类型执行相应操作
if (item.getType() == Notes.TYPE_FOLDER || item.getType() == Notes.TYPE_SYSTEM) {
openFolder(item);
} else if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
}
// 清除搜索栏内容并退出搜索模式
deactivateSearchMode();
}
}
}
private void startQueryDestinationFolders() {
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
@ -929,24 +1435,51 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(Notes.ID_TRASH_FOLDER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
/**
*
* @param folderId ID
*/
private void showPasswordSettingDialog(long folderId) {
PasswordSettingDialog dialog = new PasswordSettingDialog(this, folderId, new PasswordSettingDialog.OnPasswordSetListener() {
@Override
public void onPasswordSet(boolean success) {
if (success) {
Toast.makeText(NotesListActivity.this, "密码设置成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NotesListActivity.this, "密码设置失败", Toast.LENGTH_SHORT).show();
}
}
});
dialog.show();
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
// 为便签添加上下文菜单,支持重命名功能
mNotesListView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
menu.add(0, MENU_NOTE_RENAME, 0, "重命名");
}
}
});
// 显示上下文菜单
view.showContextMenu();
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
// 显示上下文菜单
view.showContextMenu();
}
}
return false;

@ -69,7 +69,11 @@ public class NotesListItem extends LinearLayout {
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
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 {
@ -83,10 +87,19 @@ public class NotesListItem extends LinearLayout {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
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.hasAlert()) {
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 {

@ -61,7 +61,7 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLDER) },
null);
}

Loading…
Cancel
Save