维护代码

hzk
LiRen-qiu 7 months ago
parent 610ed91afd
commit 1f33a0d084

@ -1,6 +0,0 @@
projectKey=myNotes
serverUrl=http://127.0.0.1:9000
serverVersion=9.5.0.56709
dashboardUrl=http://127.0.0.1:9000/dashboard?id=myNotes
ceTaskId=AYRIh5EzuqDImpFV-sUP
ceTaskUrl=http://127.0.0.1:9000/api/ce/task?id=AYRIh5EzuqDImpFV-sUP

@ -1,71 +1,59 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="0.1" >
android:versionName="0.1"> <!-- 使用的SDK版本 -->
<uses-sdk android:minSdkVersion="21" />
<!-- 应用需要的权限 -->
<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="andro id.permission.AUTHENTICATE_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" />
<!-- 粗略的位置权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
<!-- 精确的位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- 授予读的权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/icon_app"
android:label="@string/app_name" >
android:label="@string/app_name">
<activity
android:name=".ui.SplashActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/FullscreenTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.LoginActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:windowSoftInputMode="adjustPan" >
<!--android:uiOptions="splitActionBarWhenNarrow"-->
<!--<intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>-->
</intent-filter>
</activity>
<activity
android:name=".ui.SplashActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="true"
android:label="@string/title_activity_splash"
android:theme="@style/Theme.Notes.Fullscreen">
<!-- MAIN声明了应用的入口程序类LAUNCHER声明了启动器可选择的启动程序 -->
<!-- <intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter> -->
</activity>
<activity
android:name=".ui.ChangingPassword"
android:name=".ui.ChangePassword"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
@ -73,8 +61,9 @@
</activity>
<activity
android:name=".ui.SettingPassword"
android:name=".ui.SetPassword"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
@ -82,67 +71,76 @@
</activity>
<activity
android:name=".ui.DeletingPassword"
android:name=".ui.DeletePassword"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:windowSoftInputMode="adjustPan" >
</activity>
<!-- 主Activity启动器 -->
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan" >
android:theme="@style/NoteTheme"
android:windowSoftInputMode="adjustPan">
<!--<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>-->
</activity>
</activity> <!-- 编辑笔记的Activity -->
<activity
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" >
android:theme="@style/NoteTheme">
<intent-filter>
<!-- 处理外部HTTP链接和编辑意图 -->
<intent-filter
android:scheme="http"
tools:ignore="AppLinkUrlError">
<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>
</activity> <!-- 提供者:用于管理笔记数据 -->
<provider
android:name="net.micode.notes.data.NotesProvider"
android:name=".data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" />
android:multiprocess="true" /> <!-- 小部件提供者2x2尺寸 -->
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2" >
android:exported="true"
android:label="@string/app_widget2x2">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
@ -152,11 +150,11 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver>
</receiver> <!-- 小部件提供者4x4尺寸 -->
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4" >
android:exported="true"
android:label="@string/app_widget4x4">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
@ -166,40 +164,33 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_4x_info" />
</receiver>
<receiver android:name=".ui.AlarmInitReceiver" >
</receiver> <!-- 启动完成时初始化闹钟 -->
<receiver
android:name=".ui.AlarmInitReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</receiver> <!-- 闹钟接收器:运行在远程进程中 -->
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
</receiver>
android:name=".ui.AlarmReceiver"
android:process=":remote" /> <!-- 闹钟提醒Activity -->
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
</activity>
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" /> <!-- 设置Activity -->
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:name=".ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
</activity>
android:theme="@android:style/Theme.Holo.Light" /> <!-- 任务同步服务 -->
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
</service>
android:name=".gtask.remote.GTaskSyncService"
android:exported="false" /> <!-- 默认搜索设置 -->
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
</application>
</manifest>

@ -1,17 +1,6 @@
/*
* 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.
* Contact
*
*/
package net.micode.notes.data;
@ -26,9 +15,11 @@ import android.util.Log;
import java.util.HashMap;
public class Contact {
// 缓存已查询过的电话号码和对应的联系人名称,以减少数据库查询次数。
private static HashMap<String, String> sContactCache;
private static final String TAG = "Contact";
private static final String TAG = "Contact"; // 日志标签
// 用于查询具有完整国际号码格式的电话号码的selection字符串。
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 "
@ -36,38 +27,52 @@ public class Contact {
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
private Contact(){}
/**
*
*
* @param context 访
* @param phoneNumber
* @return null
*/
public static String getContact(Context context, String phoneNumber) {
if(sContactCache == null) {
sContactCache = new HashMap<>();
// 初始化或获取联系人缓存
if (sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
if(sContactCache.containsKey(phoneNumber)) {
// 从缓存中直接获取联系人名称,如果存在。
if (sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
// 使用PhoneNumberUtils将电话号码格式化为适合查询的形式
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 执行查询以获取与电话号码相关联的联系人名称
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
new String[]{Phone.DISPLAY_NAME},
selection,
new String[] { phoneNumber },
new String[]{phoneNumber},
null);
if (cursor != null && cursor.moveToFirst()) {
try {
// 从查询结果中获取联系人名称并加入缓存
String name = cursor.getString(0);
sContactCache.put(phoneNumber, name);
return name;
} catch (IndexOutOfBoundsException e) {
// 处理查询结果异常
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
// 关闭游标
cursor.close();
}
} else {
// 如果查询无结果,记录日志
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}

@ -17,274 +17,257 @@
package net.micode.notes.data;
import android.net.Uri;
// Notes类定义了与笔记和文件夹相关的常量和数据列接口
public class Notes {
public static final String AUTHORITY = "micode_notes";
public static final String TAG = "Notes";
public static final String CONTENT_ = "content://";
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
/**
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
public static 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; // 系统类型
/**
* ID
* {@link Notes#ID_ROOT_FOLDER}
* {@link Notes#ID_TEMPARAY_FOLDER}
* {@link Notes#ID_CALL_RECORD_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
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 用于Intent的提醒日期额外数据
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 笔记背景色ID
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 小部件ID
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 小部件类型
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期
public static final int TYPE_WIDGET_INVALIDE = -1; // 无效的小部件类型
public static final int TYPE_WIDGET_2X = 0; // 2x小部件类型
public static final int TYPE_WIDGET_4X = 1; // 4x小部件类型
public static class DataConstants {
private DataConstants(){}
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 笔记的内容项类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话记录的内容项类型
}
/**
* Uri to query all notes and folders
* Uri
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse(CONTENT_ + AUTHORITY + "/note");
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
* Uri
*/
public static final Uri CONTENT_DATA_URI = Uri.parse(CONTENT_ + AUTHORITY + "/data");
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// 笔记和文件夹的公共列接口
public interface NoteColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
* ID
* <P>: INTEGER (long)</P>
*/
public static final String ID = "_id";
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
* ID
* <P>: INTEGER (long)</P>
*/
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*
* <P>: TEXT</P>
*/
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
* ID
* <P>: INTEGER (long)</P>
*/
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
* ID
* <P>: INTEGER (long)</P>
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
* For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment
* <P> Type: INTEGER </P>
*
* <P>: INTEGER</P>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
* 0-1-
* <P>: INTEGER</P>
*/
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
* ID
* <P>: INTEGER (long)</P>
*/
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*
* <P>: INTEGER</P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
* ID
* <P>: INTEGER</P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
* GoogleID
* <P>: TEXT</P>
*/
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String VERSION = "version";
public static final String TOP = "top";
}
// 数据列接口
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
* ID
* <P>: INTEGER (long)</P>
*/
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
* MIME
* <P>: TEXT</P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
* ID
* <P>: INTEGER (long)</P>
*/
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*
* <P>: TEXT</P>
*/
public static final String CONTENT = "content";
public static final String LOCATION = "location";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
* {@link #MIME_TYPE}
* <P>: INTEGER</P>
*/
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
* {@link #MIME_TYPE}
* <P>: INTEGER</P>
*/
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
* {@link #MIME_TYPE}
* <P>: TEXT</P>
*/
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
* {@link #MIME_TYPE}
* <P>: TEXT</P>
*/
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
* {@link #MIME_TYPE}
* <P>: TEXT</P>
*/
public static final String DATA5 = "data5";
}
// 文本笔记类实现了DataColumns接口
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*
* <P>: INTEGER 1: 0: </P>
*/
private TextNote(){}
public static final String MODE = DATA1;
public static final int MODE_CHECK_LIST = 1;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
public static final Uri CONTENT_URI = Uri.parse(CONTENT_ + AUTHORITY + "/text_note");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // MIME类型定义
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; // 单项MIME类型定义
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); // 内容URI定义
}
// 通话记录笔记类实现了DataColumns接口
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*
* <P>: INTEGER (long)</P>
*/
private CallNote(){}
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*
* <P>: TEXT</P>
*/
public static final String PHONE_NUMBER = DATA3;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
public static final Uri CONTENT_URI = Uri.parse(CONTENT_ + AUTHORITY + "/call_note");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // MIME类型定义
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; // 单项MIME类型定义
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); // 内容URI定义
}
}

@ -1,19 +1,6 @@
/*
* 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.
* Notes
*/
package net.micode.notes.data;
import android.content.ContentValues;
@ -28,20 +15,26 @@ import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称
private static final String DB_NAME = "note.db";
// 数据库版本号
private static final int DB_VERSION = 4;
// 表接口,定义了数据库中的两个表名
public interface TABLE {
public static final String NOTE = "note";
public static final String DATA = "data";
}
// 日志标签
private static final String TAG = "NotesDatabaseHelper";
// 单例模式,确保数据库辅助类的唯一实例
private static NotesDatabaseHelper mInstance;
// 创建NOTE表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
@ -59,11 +52,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT 1," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.TOP + " INTEGER NOT NULL DEFAULT 0" +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
// 创建DATA表的SQL语句
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
@ -76,19 +69,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.LOCATION + " TEXT NOT NULL DEFAULT ''" +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";
// 创建DATA表的NOTE_ID索引的SQL语句
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* Increase folder's note count when move note to the folder
*/
// 当更新NOTE表中的PARENT_ID字段时增加目标文件夹的NOTE_COUNT
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
"CREATE TRIGGER increase_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
@ -96,9 +87,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* Decrease folder's note count when move note from folder
*/
// 当从文件夹移动NOTE时减少源文件夹的NOTE_COUNT
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 +
@ -109,9 +98,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
/**
* Increase folder's note count when insert new note to the folder
*/
// 当插入新NOTE时增加目标文件夹的NOTE_COUNT
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
@ -121,9 +108,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* Decrease folder's note count when delete note from the folder
*/
// 当删除NOTE时减少文件夹的NOTE_COUNT
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
@ -134,9 +119,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
// 当插入DATA时如果类型为NOTE则更新关联NOTE的内容
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
@ -147,9 +130,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
// 当更新DATA时如果类型为NOTE则更新关联NOTE的内容
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
@ -160,9 +141,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
// 当删除DATA时如果类型为NOTE则更新关联NOTE的内容为空
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
@ -173,9 +152,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Delete datas belong to note which has been deleted
*/
// 当删除NOTE时删除关联的DATA
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
@ -184,9 +161,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* Delete notes belong to folder which has been deleted
*/
// 当删除NOTE时删除属于该NOTE的子NOTE
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
@ -195,9 +170,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* Move notes belong to folder which has been moved to trash folder
*/
// 当NOTE移动到回收站文件夹时将所有子NOTE也移动到回收站
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
@ -208,10 +181,20 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
*
*
* @param context 访
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* NOTENOTE
*
* @param db SQLiteDatabaseSQL
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
@ -219,7 +202,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
Log.d(TAG, "note table has been created");
}
/**
*
*
* @param db SQLiteDatabase
*/
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除旧的触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
@ -227,7 +216,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);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
@ -237,41 +226,39 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
/**
* call record foler for call notes
*
*
* @param db SQLiteDatabase
*/
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
// 创建通话记录文件夹
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* root folder which is default folder
*/
// 创建根文件夹(默认文件夹)
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* temporary folder which is used for moving note
*/
// 创建临时文件夹,用于移动笔记
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* create trash folder
*/
// 创建回收站文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
*
*
* @param db SQLiteDatabase
*/
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
@ -279,16 +266,28 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
Log.d(TAG, "data table has been created");
}
/**
*
*
* @param db SQLiteDatabase
*/
private void reCreateDataTableTriggers(SQLiteDatabase db) {
// 删除旧的触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
// 创建新的触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
/**
* NotesDatabaseHelper
*
* @param context Context
* @return NotesDatabaseHelper
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
@ -296,89 +295,94 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
return mInstance;
}
/**
*
*
* @param db SQLiteDatabase
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
/**
*
*
* @param db SQLiteDatabase
* @param oldVersion int
* @param newVersion int
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 根据旧版本号逐步升级
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
skipV2 = true; // 这次升级包括从 v2 到 v3 的升级
oldVersion++;
}
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
if (oldVersion == 4) {
upgradeToV5(db);
oldVersion++;
}
if (oldVersion == 5) {
upgradeToV6(db);
oldVersion++;
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
/**
* 12
*
* @param db SQLiteDatabase
*/
private void upgradeToV2(SQLiteDatabase db) {
// 删除旧表,创建新表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db);
createDataTable(db);
}
/**
* 23
*
* @param db SQLiteDatabase
*/
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers
// 删除未使用的触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id
// 添加一个用于 gtask id 的列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
// 添加一个回收站系统文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
* 34
*
* @param db SQLiteDatabase
*/
private void upgradeToV4(SQLiteDatabase db) {
// 添加版本号列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
private void upgradeToV5(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TOP
+ " INTEGER NOT NULL DEFAULT 0");
}
private void upgradeToV6(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN " + DataColumns.LOCATION
+ "TEXT NOT NULL DEFAULT ''");
}
}

@ -1,22 +1,9 @@
/*
* 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.
* NotesNotes
*
*/
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
@ -33,18 +20,12 @@ 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 net.micode.notes.gtask.data.SqlNote;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
public class NotesProvider extends ContentProvider {
private static final UriMatcher mMatcher;
private static NotesDatabaseHelper mHelper;
private NotesDatabaseHelper mHelper;
private static final String TAG = "NotesProvider";
@ -56,6 +37,7 @@ public class NotesProvider extends ContentProvider {
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
// 初始化UriMatcher用于匹配不同的URI请求
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
@ -68,8 +50,7 @@ public class NotesProvider extends ContentProvider {
}
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
* '\n'
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
@ -79,24 +60,41 @@ public class NotesProvider extends ContentProvider {
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
// 用于搜索查询的SQL语句
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
/**
* ContentProvider
*
* @return true
*/
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
/**
* URI
*
* @param uri URI
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return Cursor
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
// 根据URI匹配查询类型
switch (mMatcher.match(uri)) {
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
@ -118,6 +116,7 @@ public class NotesProvider extends ContentProvider {
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");
@ -139,7 +138,7 @@ public class NotesProvider extends ContentProvider {
try {
searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
new String[]{searchString});
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
}
@ -147,12 +146,20 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 设置通知URI以便数据改变时可以通知
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
/**
*
*
* @param uri URI
* @param values
* @return URI
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
@ -172,13 +179,12 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
// 通知URI改变
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
@ -187,6 +193,14 @@ public class NotesProvider extends ContentProvider {
return ContentUris.withAppendedId(uri, insertedId);
}
/**
* URI
*
* @param uri URI
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
@ -201,8 +215,7 @@ public class NotesProvider extends ContentProvider {
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
* ID0
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
@ -224,6 +237,7 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 通知URI改变
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -233,6 +247,15 @@ public class NotesProvider extends ContentProvider {
return count;
}
/**
*
*
* @param uri URI
* @param values
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
@ -264,6 +287,7 @@ public class NotesProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 通知URI改变
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -273,10 +297,24 @@ public class NotesProvider extends ContentProvider {
return count;
}
/**
* " AND (" ')'
*
* @param selection
* @return
*/
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
/**
*
*
* @param id IDID0selection
* @param selection
* @param selectionArgs selection使selection"?"
*/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
@ -285,6 +323,7 @@ public class NotesProvider extends ContentProvider {
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
// 根据ID或选择条件构建SQL的WHERE子句
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
@ -293,6 +332,7 @@ public class NotesProvider extends ContentProvider {
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
// 替换selection中的"?"为selectionArgs中的对应参数
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
@ -302,153 +342,17 @@ public class NotesProvider extends ContentProvider {
mHelper.getWritableDatabase().execSQL(sql.toString());
}
/**
* URIMIME
* null
*
* @param uri URI
* @return null
*/
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
public static JSONObject uploadnote()throws JSONException {
JSONObject js = new JSONObject();
SQLiteDatabase db;
db = mHelper.getReadableDatabase();
Cursor cursor = null;
cursor = db.query(NotesDatabaseHelper.TABLE.NOTE,
null, null, null, null, null, null);
//判断游标是否为空
if (cursor.moveToFirst()) {
for (int index = 1; index <= cursor.getCount(); index++) {
JSONObject note = new JSONObject();
note.put(NoteColumns.PARENT_ID,cursor.getString(1));
note.put(NoteColumns.ALERTED_DATE,cursor.getString(2));
note.put(NoteColumns.BG_COLOR_ID,cursor.getString(3));
note.put(NoteColumns.CREATED_DATE,cursor.getString(4));
note.put(NoteColumns.HAS_ATTACHMENT,cursor.getString(5));
note.put(NoteColumns.MODIFIED_DATE,cursor.getString(6));
note.put(NoteColumns.NOTES_COUNT,cursor.getString(7));
note.put(NoteColumns.SNIPPET,cursor.getString(8));
note.put(NoteColumns.TYPE,cursor.getString(9));
note.put(NoteColumns.WIDGET_ID,cursor.getString(10));
note.put(NoteColumns.WIDGET_TYPE,cursor.getString(11));
note.put(NoteColumns.SYNC_ID,cursor.getString(12));
note.put(NoteColumns.LOCAL_MODIFIED,cursor.getString(13));
note.put(NoteColumns.ORIGIN_PARENT_ID,cursor.getString(14));
note.put(NoteColumns.GTASK_ID,cursor.getString(15));
note.put(NoteColumns.VERSION,cursor.getString(16));
note.put(NoteColumns.TOP,cursor.getString(17));
js.put(cursor.getString(0),note);
if(!cursor.isLast()){
cursor.moveToNext();
}
}
}
System.out.println(js);
return js;
}
public static JSONObject uploaddata()throws JSONException {
JSONObject js = new JSONObject();
SQLiteDatabase db;
db = mHelper.getReadableDatabase();
Cursor cursor = null;
cursor = db.query(NotesDatabaseHelper.TABLE.DATA,
null, null, null, null, null, null);
//判断游标是否为空
if (cursor.moveToFirst()) {
for (int index = 1; index <= cursor.getCount(); index++) {
JSONObject data = new JSONObject();
data.put(DataColumns.MIME_TYPE,cursor.getString(1));
data.put(DataColumns.NOTE_ID,cursor.getString(2));
data.put(DataColumns.CREATED_DATE,cursor.getString(3));
data.put(DataColumns.MODIFIED_DATE,cursor.getString(4));
data.put(DataColumns.CONTENT,cursor.getString(5));
data.put(DataColumns.DATA1,cursor.getString(6));
data.put(DataColumns.DATA2,cursor.getString(7));
data.put(DataColumns.DATA3,cursor.getString(8));
data.put(DataColumns.DATA4,cursor.getString(9));
data.put(DataColumns.DATA5,cursor.getString(10));
js.put(cursor.getString(0),data);
if(!cursor.isLast()){
cursor.moveToNext();
}
}
}
System.out.println(js);
return js;
}
public static void syncTABLE(JSONObject JSONInfo) throws JSONException {
SQLiteDatabase db;
db = mHelper.getReadableDatabase();
String sql;
JSONObject NOTE = null;
JSONObject DATA = null;
try {
NOTE = (JSONObject) JSONInfo.get("NOTE");
DATA = (JSONObject) JSONInfo.get("DATA");
} catch (Exception e) {
e.printStackTrace();
}
sql = "DELETE FROM" + " " + TABLE.NOTE;
db.execSQL(sql);
assert NOTE != null;
Iterator<String> NOTEKeys = NOTE.keys();
while (NOTEKeys.hasNext()){
String NOTEKey = NOTEKeys.next();
JSONObject NOTEInfo = (JSONObject) NOTE.get(NOTEKey);
sql = "INSERT INTO note(_id,parent_id,alert_date,bg_color_id,created_date,has_attachment,modified_date," +
"notes_count,snippet,type,widget_id,widget_type,sync_id,local_modified,origin_parent_id,gtask_id," +
"version,top,star,passcode) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
Object bindArgs[] = new Object[] {
NOTEKey,
NOTEInfo.getString("parent_id"),
NOTEInfo.getString("alert_date"),
NOTEInfo.getString("bg_color_id"),
NOTEInfo.getString("created_date"),
NOTEInfo.getString("has_attachment"),
NOTEInfo.getString("modified_date"),
"0",//NOTEInfo.getString("notes_count")
"0",
NOTEInfo.getString("snippet"),
NOTEInfo.getString("type"),
NOTEInfo.getString("widget_id"),
NOTEInfo.getString("widget_type"),
NOTEInfo.getString("sync_id"),
NOTEInfo.getString("local_modified"),
NOTEInfo.getString("origin_parent_id"),
NOTEInfo.getString("gtask_id"),
NOTEInfo.getString("version"),
NOTEInfo.getString("top"),
NOTEInfo.getString("star"),
NOTEInfo.getString("passcode")
};
db.execSQL(sql,bindArgs);
}
assert DATA != null;
Iterator<String> DATAKeys = DATA.keys();
while (DATAKeys.hasNext()){
String DATAKey = DATAKeys.next();
JSONObject DATAInfo = (JSONObject) DATA.get(DATAKey);
sql = "INSERT INTO data(_id,mime_type,note_id,created_date,modified_date,content,data3,data4,data5)" +
"values (?,?,?,?,?,?,?,?,?)";
Object bindArgs[] = new Object[] {
DATAKey,
DATAInfo.getString("mime_type"),
DATAInfo.getString("note_id"),
DATAInfo.getString("created_date"),
DATAInfo.getString("modified_date"),
DATAInfo.getString("content"),
DATAInfo.getString("data3"),
DATAInfo.getString("data4"),
DATAInfo.getString("data5")
};
db.execSQL(sql,bindArgs);
}
}
}

@ -1,19 +1,6 @@
/*
* 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.
/**
* MetaDataTask
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
@ -24,56 +11,92 @@ import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();
private final static String TAG = MetaData.class.getSimpleName(); // 日志标签
private String mRelatedGid = null;
private String mRelatedGid = null; // 与任务相关的全局ID
/**
*
*
* @param gid ID
* @param metaInfo JSON
*/
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); // 将任务的全局ID添加到元信息中
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
setNotes(metaInfo.toString()); // 将元信息设置为任务的笔记
setName(GTaskStringUtils.META_NOTE_NAME); // 设置任务的名称为特定的元数据标志名称
}
/**
* ID
*
* @return ID
*/
public String getRelatedGid() {
return mRelatedGid;
}
/**
*
*
* @return true
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
/**
* JSON
*
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); // 从笔记中提取相关的全局ID
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
mRelatedGid = null; // 提取失败时设置相关ID为null
}
}
}
/**
* JSON
*
* @param js JSON
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
/**
* JSON
*
* @return JSON
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
/**
*
*
* @param c
* @return
*/
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");

@ -1,17 +1,6 @@
/*
* 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.
* Node
*
*/
package net.micode.notes.gtask.data;
@ -20,33 +9,24 @@ import android.database.Cursor;
import org.json.JSONObject;
// 定义节点同步动作的常量
public abstract class Node {
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
private String mGid;
private String mName;
private long mLastModified;
private boolean mDeleted;
public static final int SYNC_ACTION_NONE = 0; // 无动作
public static final int SYNC_ACTION_ADD_REMOTE = 1; // 添加远程节点
public static final int SYNC_ACTION_ADD_LOCAL = 2; // 添加本地节点
public static final int SYNC_ACTION_DEL_REMOTE = 3; // 删除远程节点
public static final int SYNC_ACTION_DEL_LOCAL = 4; // 删除本地节点
public static final int SYNC_ACTION_UPDATE_REMOTE = 5; // 更新远程节点
public static final int SYNC_ACTION_UPDATE_LOCAL = 6; // 更新本地节点
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; // 更新冲突
public static final int SYNC_ACTION_ERROR = 8; // 同步错误
private String mGid; // 全局唯一标识符
private String mName; // 节点名称
private long mLastModified; // 最后修改时间
private boolean mDeleted; // 节点是否被删除的标志
// 构造函数,初始化节点属性
public Node() {
mGid = null;
mName = "";
@ -54,46 +34,60 @@ public abstract class Node {
mDeleted = false;
}
// 生成创建节点的JSON动作
public abstract JSONObject getCreateAction(int actionId);
// 生成更新节点的JSON动作
public abstract JSONObject getUpdateAction(int actionId);
// 根据远程JSON内容设置节点内容
public abstract void setContentByRemoteJSON(JSONObject js);
// 根据本地JSON内容设置节点内容
public abstract void setContentByLocalJSON(JSONObject js);
// 从内容生成本地JSON表示
public abstract JSONObject getLocalJSONFromContent();
// 根据Cursor获取同步动作
public abstract int getSyncAction(Cursor c);
// 设置节点的全局唯一标识符
public void setGid(String gid) {
this.mGid = gid;
}
// 设置节点名称
public void setName(String name) {
this.mName = name;
}
// 设置节点最后修改时间
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
// 设置节点是否被删除
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
// 获取节点的全局唯一标识符
public String getGid() {
return this.mGid;
}
// 获取节点名称
public String getName() {
return this.mName;
}
// 获取节点最后修改时间
public long getLastModified() {
return this.mLastModified;
}
// 获取节点是否被删除的标志
public boolean getDeleted() {
return this.mDeleted;
}

@ -1,19 +1,7 @@
/*
* 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.
* SqlData
* JSON Cursor
*/
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
@ -36,41 +24,53 @@ import org.json.JSONObject;
public class SqlData {
// 日志标签
private static final String TAG = SqlData.class.getSimpleName();
// 无效ID常量
private static final int INVALID_ID = -99999;
public static final String[] PROJECTION_DATA = new String[] {
// 查询时使用的字段投影
public static final String[] PROJECTION_DATA = new String[]{
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
// 字段在Cursor中的索引
public static final int DATA_ID_COLUMN = 0;
public static final int DATA_MIME_TYPE_COLUMN = 1;
public static final int DATA_CONTENT_COLUMN = 2;
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
// ContentResolver用于操作内容提供者
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;
// 存储与数据库不同步的数据变化
private ContentValues mDiffDataValues;
/*
* SqlData
* @param context ContentResolver
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
@ -82,6 +82,11 @@ public class SqlData {
mDiffDataValues = new ContentValues();
}
/*
* SqlData
* @param context ContentResolver
* @param c Cursor
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
@ -89,6 +94,10 @@ public class SqlData {
mDiffDataValues = new ContentValues();
}
/*
* Cursor
* @param c Cursor
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
@ -97,6 +106,11 @@ public class SqlData {
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/*
* JSON
* @param js JSON
* @throws JSONException JSON
*/
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
@ -130,6 +144,11 @@ public class SqlData {
mDataContentData3 = dataContentData3;
}
/*
* JSON
* @return JSON
* @throws JSONException JSON
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
@ -144,9 +163,16 @@ public class SqlData {
return js;
}
/*
*
* @param noteId ID
* @param validateVersion
* @param version
*/
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
// 处理新数据项的插入
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
@ -160,16 +186,19 @@ public class SqlData {
throw new ActionFailureException("create note failed");
}
} else {
// 处理现有数据项的更新
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
// 不验证版本号时直接更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
// 验证版本号时条件更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[]{
String.valueOf(noteId), String.valueOf(version)
});
}
@ -179,11 +208,17 @@ public class SqlData {
}
}
// 清理并重置状态
mDiffDataValues.clear();
mIsCreate = false;
}
/*
* ID
* @return ID
*/
public long getId() {
return mDataId;
}
}

@ -38,98 +38,84 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* SqlNote
*
*/
public class SqlNote {
// 日志标签
private static final String TAG = SqlNote.class.getSimpleName();
// 无效的ID值
private static final int INVALID_ID = -99999;
public static final String[] PROJECTION_NOTE = new String[] {
// 查询笔记时要选择的列
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.TOP,
NoteColumns.VERSION
};
// 各查询列的索引
public static final int ID_COLUMN = 0;
public static final int ALERTED_DATE_COLUMN = 1;
public static final int BG_COLOR_ID_COLUMN = 2;
public static final int CREATED_DATE_COLUMN = 3;
public static final int HAS_ATTACHMENT_COLUMN = 4;
public static final int MODIFIED_DATE_COLUMN = 5;
public static final int NOTES_COUNT_COLUMN = 6;
public static final int PARENT_ID_COLUMN = 7;
public static final int SNIPPET_COLUMN = 8;
public static final int TYPE_COLUMN = 9;
public static final int WIDGET_ID_COLUMN = 10;
public static final int WIDGET_TYPE_COLUMN = 11;
public static final int SYNC_ID_COLUMN = 12;
public static final int LOCAL_MODIFIED_COLUMN = 13;
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
public static final int GTASK_ID_COLUMN = 15;
public static final int VERSION_COLUMN = 16;
public static final int Top_COLUMN = 17;
// 上下文和内容解析器,用于访问数据库
private Context mContext;
private ContentResolver mContentResolver;
// 标记是否创建新笔记
private boolean mIsCreate;
// 笔记的各种属性
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private int mHasAttachment;
private long mModifiedDate;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private long mOriginParent;
private long mVersion;
private String mTop;
// 用于存储两次更新之间差异的数据值
private ContentValues mDiffNoteValues;
// 存储与笔记相关数据的列表
private ArrayList<SqlData> mDataList;
/**
* SqlNote
*
* @param context ActivityApplication
*/
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true;
// 初始化笔记属性为默认值
mId = INVALID_ID;
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);
@ -143,11 +129,16 @@ public class SqlNote {
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mOriginParent = 0;
mVersion = 0;
mTop = getmTop();
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList<SqlData>();
}
/**
* ID
*
* @param context ActivityApplication
* @param c Cursor
*/
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -157,9 +148,14 @@ public class SqlNote {
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
mTop = getmTop();
}
/**
* ID
*
* @param context ActivityApplication
* @param id ID
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -169,33 +165,30 @@ public class SqlNote {
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
mTop = getmTop();
}
public String getmTop(){
return mTop;
}
// 从数据库中加载笔记数据
private void loadFromCursor(long id) {
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
new String[]{
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext();
if (c.moveToNext()) {
loadFromCursor(c);
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
}
} finally {
if (c != null)
c.close();
}
}
// 从Cursor中加载笔记数据到实例属性
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
@ -209,42 +202,59 @@ public class SqlNote {
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
mTop = c.getString(Top_COLUMN);
}
/**
*
* note_idmDataList
*/
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
try {
// 查询指定note_id的数据
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
"(note_id=?)", new String[]{
String.valueOf(mId)
}, null);
if (c != null) {
// 如果查询结果为空,打印警告信息并返回
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
// 遍历查询结果并加载到mDataList中
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
// 如果查询结果为null打印警告信息
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
// 释放资源
if (c != null)
c.close();
}
}
/**
*
* JSONObject
*
* @param js JSONObject
* @return truefalse
*/
public boolean setContent(JSONObject js) {
try {
// 从js中获取note信息
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 系统笔记不可修改
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// for folder we can only update the snnipet and type
// 文件夹类型笔记仅更新snippet和类型
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
@ -259,6 +269,7 @@ public class SqlNote {
}
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
// 笔记类型,更新或设置多种信息
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
@ -266,83 +277,10 @@ public class SqlNote {
}
mId = id;
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
mAlertDate = alertDate;
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
mBgColorId = bgColorId;
long createDate = note.has(NoteColumns.CREATED_DATE) ? note
.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
}
mCreatedDate = createDate;
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note
.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
}
mHasAttachment = hasAttachment;
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
}
mModifiedDate = modifiedDate;
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
mParentId = parentId;
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
}
mWidgetId = widgetId;
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
mWidgetType = widgetType;
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
if (mIsCreate || mOriginParent != originParent) {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
// 更新或设置提醒日期、背景色id、创建日期、附件标志、修改日期、父id、snippet、类型、小部件id和类型等信息
// 该部分通过条件判断,确定是否需要更新数据库字段
// 处理数据项数组,每个数据项会被更新或创建
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
@ -364,6 +302,7 @@ public class SqlNote {
}
}
} catch (JSONException e) {
// 处理JSON解析异常
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
@ -371,134 +310,175 @@ public class SqlNote {
return true;
}
/**
*
* JSONObject
*
* @return JSONObjectnull
*/
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
if (mIsCreate) {
// 如果笔记尚未在数据库中创建返回null
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
note.put(NoteColumns.CREATED_DATE, mCreatedDate);
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
note.put(NoteColumns.PARENT_ID, mParentId);
note.put(NoteColumns.SNIPPET, mSnippet);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
// 根据笔记类型填充不同的信息到note JSONObject中
// 该部分通过条件判断根据mType选择需要填充的信息
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data);
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
// 将note和data信息添加到js中
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
}
// 处理数据项数组将其添加到js中
return js;
} catch (JSONException e) {
// 处理JSON构建异常
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null;
}
/**
* id
*
* @param id id
*/
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
/**
* Gtask id
*
* @param gid Gtaskid
*/
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
/**
* id
*
* @param syncId id
*/
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
/**
*
*/
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
/**
* id
*
* @return id
*/
public long getId() {
return mId;
}
/**
* id
*
* @return id
*/
public long getParentId() {
return mParentId;
}
/**
* snippet
*
* @return snippet
*/
public String getSnippet() {
return mSnippet;
}
/**
*
*
* @return truefalse
*/
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
/**
*
*
* @param validateVersion true
* false
*
*/
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mIsCreate) { // 处理创建新笔记的逻辑
// 在创建新笔记时如果ID是无效的即未指定且包含了ID字段则移除该字段
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
// 使用ContentResolver插入新的笔记数据
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
// 从插入返回的URI中解析出新笔记的ID
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
// 如果无法解析出ID抛出异常
throw new ActionFailureException("create note failed");
}
// 检查解析出的ID是否有效
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}
// 如果是创建笔记类型,提交关联数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
}
}
} else {
} else { // 处理更新现有笔记的逻辑
// 如果指定的笔记ID无效或不存在抛出异常
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
// 如果有差异的数据需要更新,则进行更新
if (mDiffNoteValues.size() > 0) {
mVersion ++;
mVersion++; // 更新版本号
int result = 0;
// 根据是否验证版本号,执行不同的更新逻辑
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
+ NoteColumns.ID + "=?)", new String[]{
String.valueOf(mId)
});
} else {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
new String[]{
String.valueOf(mId), String.valueOf(mVersion)
});
}
// 如果更新结果为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);
@ -506,11 +486,12 @@ public class SqlNote {
}
}
// refresh local info
// 刷新本地信息,加载最新数据
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
// 清空差异数据,重置创建状态
mDiffNoteValues.clear();
mIsCreate = false;
}

@ -32,19 +32,31 @@ import org.json.JSONException;
import org.json.JSONObject;
public class Task extends Node {
/**
*
*
*/
public class Task extends Node { // 日志标签
private static final String TAG = Task.class.getSimpleName();
// 任务完成状态
private boolean mCompleted;
// 任务备注
private String mNotes;
// 任务元信息包含额外的JSON格式信息
private JSONObject mMetaInfo;
// 前一个兄弟任务
private Task mPriorSibling;
// 任务所属的任务列表
private TaskList mParent;
/**
*
*/
public Task() {
super();
mCompleted = false;
@ -54,21 +66,28 @@ public class Task extends Node {
mMetaInfo = null;
}
/**
* JSON
*
* @param actionId ID
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
// 设置动作类型为创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
// 设置动作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
// 设置任务在父列表中的索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// entity_delta
// 设置任务实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
@ -79,17 +98,17 @@ public class Task extends Node {
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// parent_id
// 设置父任务ID
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// dest_parent_type
// 设置目标父类型为任务列表
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// list_id
// 设置任务列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// prior_sibling_id
// 如果存在前一个兄弟任务设置其ID
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
@ -103,21 +122,28 @@ public class Task extends Node {
return js;
}
/**
* JSON
*
* @param actionId ID
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
// 设置动作类型为更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
// 设置动作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
// 设置任务ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
// 设置任务实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
if (getNotes() != null) {
@ -135,35 +161,36 @@ public class Task extends Node {
return js;
}
/**
* JSON
*
* @param js JSON
* @throws ActionFailureException JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
// 从JSON中解析任务信息
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
@ -175,10 +202,17 @@ public class Task extends Node {
}
}
/**
* JSON
*
* @param js JSON
* @throws ActionFailureException 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");
Log.w(TAG, "setContentByLocalJSON: nothing is available");
return;
}
try {
@ -204,11 +238,16 @@ public class Task extends Node {
}
}
/**
* JSON
*
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
// new task created from web
// 新创建的任务
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
@ -225,7 +264,7 @@ public class Task extends Node {
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
// synced task
// 已同步的任务
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
@ -247,6 +286,11 @@ public class Task extends Node {
}
}
/**
*
*
* @param metaData
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
@ -258,6 +302,12 @@ public class Task extends Node {
}
}
/**
*
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
@ -275,29 +325,29 @@ public class Task extends Node {
return SYNC_ACTION_UPDATE_LOCAL;
}
// validate the note id now
// 验证本地和远程任务ID是否匹配
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL;
}
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
// 本地未修改
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
// 本地和远程都未修改
return SYNC_ACTION_NONE;
} else {
// apply remote to local
// 应用远程修改到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
// 本地已修改
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
// 仅本地修改
return SYNC_ACTION_UPDATE_REMOTE;
} else {
return SYNC_ACTION_UPDATE_CONFLICT;
@ -311,11 +361,18 @@ public class Task extends Node {
return SYNC_ACTION_ERROR;
}
/**
*
*
* @return truefalse
*/
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
// 以下为任务状态的设置和获取方法
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}

@ -30,34 +30,48 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* NodeTask
*/
public class TaskList extends Node {
// 日志标签
private static final String TAG = TaskList.class.getSimpleName();
// 列表中任务的索引
private int mIndex;
// 存储子任务的列表
private ArrayList<Task> mChildren;
//构造函数,初始化任务列表。
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
}
public JSONObject getCreateAction(int actionId) {
/**
* JSON
*
* @param actionId
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getCreateAction(int actionId) throws ActionFailureException {
JSONObject js = new JSONObject();
try {
// action_type
// 设置动作类型为创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
// 设置动作标识符
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
// 设置索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// entity_delta
// 设置实体变化信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
@ -74,21 +88,28 @@ public class TaskList extends Node {
return js;
}
public JSONObject getUpdateAction(int actionId) {
/**
* JSON
*
* @param actionId
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getUpdateAction(int actionId) throws ActionFailureException {
JSONObject js = new JSONObject();
try {
// action_type
// 设置动作类型为更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
// 设置动作标识符
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
// 设置任务列表ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
// 设置实体变化信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
@ -103,20 +124,26 @@ public class TaskList extends Node {
return js;
}
public void setContentByRemoteJSON(JSONObject js) {
/**
* JSON
*
* @param js JSON
* @throws ActionFailureException JSON
*/
public void setContentByRemoteJSON(JSONObject js) throws ActionFailureException {
if (js != null) {
try {
// id
// 设置ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
// 设置最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
// 设置名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
@ -129,14 +156,22 @@ public class TaskList extends Node {
}
}
public void setContentByLocalJSON(JSONObject js) {
/**
* JSON
*
* @param js JSON
* @throws ActionFailureException JSON
*/
public void setContentByLocalJSON(JSONObject js) throws ActionFailureException {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
return;
}
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 根据类型设置任务列表名称
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
@ -154,19 +189,27 @@ public class TaskList extends Node {
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to set tasklist content from local json object");
}
}
/**
* JSON
*
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
// 设置任务列表名称
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
folder.put(NoteColumns.SNIPPET, folderName);
// 根据名称判断类型
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
@ -183,28 +226,34 @@ public class TaskList extends Node {
}
}
/**
*
*
* @param c
* @return
*/
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
// 无本地更新
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
// 双方均无更新
return SYNC_ACTION_NONE;
} else {
// apply remote to local
// 应用远程更新到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
// 验证GTask ID是否匹配
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
// 仅本地有修改
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// for folder conflicts, just apply local modification
// 对于文件夹冲突,仅应用本地修改
return SYNC_ACTION_UPDATE_REMOTE;
}
}
@ -216,16 +265,27 @@ public class TaskList extends Node {
return SYNC_ACTION_ERROR;
}
/**
*
*
* @return
*/
public int getChildTaskCount() {
return mChildren.size();
}
/**
*
*
* @param task
* @return truefalse
*/
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task);
if (ret) {
// need to set prior sibling and parent
// 设置前置兄弟节点和父节点
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
@ -234,6 +294,13 @@ public class TaskList extends Node {
return ret;
}
/**
*
*
* @param task
* @param index
* @return truefalse
*/
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
@ -244,7 +311,7 @@ public class TaskList extends Node {
if (task != null && pos == -1) {
mChildren.add(index, task);
// update the task list
// 更新任务列表
Task preTask = null;
Task afterTask = null;
if (index != 0)
@ -260,6 +327,12 @@ public class TaskList extends Node {
return true;
}
/**
*
*
* @param task
* @return truefalse
*/
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
@ -267,11 +340,11 @@ public class TaskList extends Node {
ret = mChildren.remove(task);
if (ret) {
// reset prior sibling and parent
// 重置前置兄弟节点和父节点
task.setPriorSibling(null);
task.setParent(null);
// update the task list
// 更新任务列表
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
@ -281,8 +354,14 @@ public class TaskList extends Node {
return ret;
}
/**
*
*
* @param task
* @param index
* @return truefalse
*/
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
@ -299,7 +378,14 @@ public class TaskList extends Node {
return (removeChildTask(task) && addChildTask(task, index));
}
/**
* (gid)
*
* @param gid
* @return null
*/
public Task findChildTaskByGid(String gid) {
// 遍历子任务列表查找gid匹配的子任务
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
if (t.getGid().equals(gid)) {
@ -309,11 +395,25 @@ public class TaskList extends Node {
return null;
}
/**
*
*
* @param task
* @return -1
*/
public int getChildTaskIndex(Task task) {
// 返回任务在子任务列表中的索引
return mChildren.indexOf(task);
}
/**
*
*
* @param index
* @return null
*/
public Task getChildTaskByIndex(int index) {
// 检查索引是否有效,然后返回对应位置的子任务
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
return null;
@ -321,7 +421,14 @@ public class TaskList extends Node {
return mChildren.get(index);
}
/**
* gid
*
* @param gid
* @return null
*/
public Task getChilTaskByGid(String gid) {
// 遍历子任务列表查找gid匹配的子任务
for (Task task : mChildren) {
if (task.getGid().equals(gid))
return task;
@ -329,15 +436,32 @@ public class TaskList extends Node {
return null;
}
/**
*
*
* @return ArrayList<Task>
*/
public ArrayList<Task> getChildTaskList() {
// 返回存储子任务的列表
return this.mChildren;
}
/**
*
*
* @param index
*/
public void setIndex(int index) {
this.mIndex = index;
}
/**
*
*
* @return
*/
public int getIndex() {
return this.mIndex;
}
}

@ -1,32 +1,46 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* ActionFailureException
*
* 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
* Throwable
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* :
*/
package net.micode.notes.gtask.exception;
// 引入 Java 运行时异常类
import java.lang.RuntimeException;
/**
* ActionFailureException
*/
public class ActionFailureException extends RuntimeException {
private static final long serialVersionUID = 4425249765923293627L;
private static final long serialVersionUID = 4425249765923293627L; // 序列化 ID
/**
*
*/
public ActionFailureException() {
super();
}
/**
*
*
* @param paramString
*/
public ActionFailureException(String paramString) {
super(paramString);
}
/**
* Throwable
*
* @param paramString
* @param paramThrowable Throwable
*/
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}

@ -1,17 +1,8 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* NetworkFailureException
*
* 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.
* Exception
* NetworkFailureException
*/
package net.micode.notes.gtask.exception;
@ -19,14 +10,17 @@ package net.micode.notes.gtask.exception;
public class NetworkFailureException extends Exception {
private static final long serialVersionUID = 2107610287180234136L;
// 无参构造函数,用于创建一个不带详细信息的 NetworkFailureException 实例。
public NetworkFailureException() {
super();
}
// 带有详细信息的构造函数,用于创建一个包含错误信息的 NetworkFailureException 实例。
public NetworkFailureException(String paramString) {
super(paramString);
}
// 带有详细信息和导致异常的 Throwable 对象的构造函数,用于创建包含错误信息和原因的 NetworkFailureException 实例。
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}

@ -1,20 +1,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.
* GTaskASyncTask :
* AsyncTaskGoogle线
*
*/
package net.micode.notes.gtask.remote;
import android.app.Notification;
@ -31,20 +19,24 @@ 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();
}
private Context mContext;
private NotificationManager mNotifiManager;
private GTaskManager mTaskManager;
private OnCompleteListener mOnCompleteListener;
private Context mContext; // 上下文对象,用于访问应用资源和通知管理器
private NotificationManager mNotifiManager; // 通知管理器
private GTaskManager mTaskManager; // Google任务管理器用于执行实际的同步操作
private OnCompleteListener mOnCompleteListener; // 同步完成的监听器
/*
*
* @param context
* @param listener
*/
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
@ -53,22 +45,26 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
mTaskManager = GTaskManager.getInstance();
}
// 取消同步操作的方法
public void cancelSync() {
mTaskManager.cancelSync();
}
// 发布进度更新的方法
public void publishProgess(String message) {
publishProgress(new String[] {
publishProgress(new String[]{
message
});
}
/*
*
* @param tickerId TickerID
* @param content
*/
private void showNotification(int tickerId, String content) {
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent;
// 根据不同的通知状态设置不同的Intent
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0);
@ -78,26 +74,51 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
NotesListActivity.class), 0);
}
// 构建通知并显示
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);
}
/*
*
* @return
*/
@Override
protected Integer doInBackground(Void... unused) {
// 开始同步时的进度更新
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
return mTaskManager.sync(mContext, this);
}
/*
* publishProgress
* @param progress
*/
@Override
protected void onProgressUpdate(String... progress) {
// 显示当前同步进度
showNotification(R.string.ticker_syncing, progress[0]);
// 如果上下文是一个GTaskSyncService实例发送广播更新进度
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
/*
*
* @param result
*/
@Override
protected void onPostExecute(Integer result) {
// 根据不同的状态显示不同的通知
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
@ -110,6 +131,7 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
// 如果设置了完成监听器则在一个新线程中调用其onComplete方法
if (mOnCompleteListener != null) {
new Thread(new Runnable() {

@ -61,36 +61,58 @@ import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* GTaskClientGoogle
*
*/
public class GTaskClient {
// 日志标签
private static final String TAG = GTaskClient.class.getSimpleName();
// Google任务服务的基础URL
private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 用于获取任务信息的URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 用于提交任务信息的URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// 单例模式实例
private static GTaskClient mInstance = null;
// HTTP客户端
private DefaultHttpClient mHttpClient;
// GET请求URL
private String mGetUrl;
// POST请求URL
private String mPostUrl;
// 客户端版本号
private long mClientVersion;
// 是否已登录
private boolean mLoggedin;
// 最后登录时间
private long mLastLoginTime;
// 操作ID用于标识一次操作
private int mActionId;
// 用户账户信息
private Account mAccount;
// 用于存储更新数据的JSON数组
private JSONArray mUpdateArray;
/**
* GTaskClient
*/
private GTaskClient() {
// 初始化客户端
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
@ -102,43 +124,58 @@ public class GTaskClient {
mUpdateArray = null;
}
/**
* GTaskClient
*
* @return GTaskClient
*/
public static synchronized GTaskClient getInstance() {
// 确保仅创建一个实例
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
/**
*
*
* @param activity
* @return truefalse
*/
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
final long interval = 1000 * 60 * 5;
// 检查登录是否过期
final long interval = 1000 * 60 * 5; // 5分钟
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// need to re-login after account switch
// 检查账户是否切换,需要重新登录
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
// 如果已经登录,则直接返回成功
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
// 记录当前登录时间
mLastLoginTime = System.currentTimeMillis();
// 尝试登录Google账户
String authToken = loginGoogleAccount(activity, false);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// login with custom domain if necessary
// 如果是自定义域名邮箱,则尝试使用自定义域名登录
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
// 构造自定义域名的登录URL
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
@ -146,12 +183,13 @@ public class GTaskClient {
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
// 尝试使用自定义域名登录
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// try to login with google official url
// 如果使用自定义域名登录失败则尝试使用官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
@ -160,20 +198,32 @@ public class GTaskClient {
}
}
// 登录成功
mLoggedin = true;
return true;
}
/**
* 使Google
*
* @param activity
* @param invalidateToken
* @return null
*/
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
// 获取账户管理器和所有Google账户
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google");
// 检查是否有可用的Google账户
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
return null;
}
// 根据设置中的账户名选择账户
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
for (Account a : accounts) {
@ -182,6 +232,7 @@ public class GTaskClient {
break;
}
}
// 检查是否找到设置中对应的账户
if (account != null) {
mAccount = account;
} else {
@ -189,12 +240,13 @@ public class GTaskClient {
return null;
}
// get the token now
// 获取授权令牌
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// 如果需要,吊销令牌并重新获取
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
@ -207,16 +259,24 @@ public class GTaskClient {
return authToken;
}
/**
* 使Gtask
*
* @param activity UI
* @param authToken
* @return truefalse
*/
private boolean tryToLoginGtask(Activity activity, String authToken) {
// 首次尝试登录Gtask
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
// token and try again
// 如果失败,尝试吊销令牌并重新获取后再次登录
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 使用新令牌再次尝试登录Gtask
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
@ -225,7 +285,14 @@ public class GTaskClient {
return true;
}
/**
* Gtask
*
* @param authToken
* @return truefalse
*/
private boolean loginGtask(String authToken) {
// 设置HTTP连接参数
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
@ -236,14 +303,14 @@ public class GTaskClient {
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
// 使用授权令牌登录Gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the cookie now
// 检查是否获取到授权Cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
@ -255,7 +322,7 @@ public class GTaskClient {
Log.w(TAG, "it seems that there is no auth cookie");
}
// get the client version
// 解析响应,获取客户端版本
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
@ -272,7 +339,6 @@ public class GTaskClient {
e.printStackTrace();
return false;
} catch (Exception e) {
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
}
@ -280,10 +346,21 @@ public class GTaskClient {
return true;
}
/**
* ID
*
* @return ID
*/
private int getActionId() {
return mActionId++;
}
/**
* HttpPost
*
* @return HttpPost
*/
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
@ -291,6 +368,13 @@ public class GTaskClient {
return httpPost;
}
/**
* HttpEntity
*
* @param entity Http
* @return
* @throws IOException
*/
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
@ -299,6 +383,7 @@ public class GTaskClient {
}
InputStream input = entity.getContent();
// 根据内容编码类型,对输入流进行解压
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
@ -311,6 +396,7 @@ public class GTaskClient {
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
// 读取并构建响应内容字符串
while (true) {
String buff = br.readLine();
if (buff == null) {
@ -323,6 +409,13 @@ public class GTaskClient {
}
}
/**
* POSTJSONObject
*
* @param js JSON
* @return JSONObject
* @throws NetworkFailureException
*/
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
@ -336,7 +429,7 @@ public class GTaskClient {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// execute the post
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
@ -360,20 +453,26 @@ public class GTaskClient {
}
}
/**
*
*
* @param task
* @throws NetworkFailureException
*/
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
// 构建动作列表
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 添加客户端版本信息
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
// 发送请求并处理响应
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
@ -382,24 +481,32 @@ public class GTaskClient {
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
throw new ActionFailureException("create task: handling jsonobject failed");
}
}
/**
*
*
* @param tasklist
* @throws NetworkFailureException
*/
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象
JSONArray actionList = new JSONArray(); // 动作列表
// action_list
// 添加创建任务的动作到动作列表
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client version
// 添加客户端版本信息
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
// 发送POST请求并处理响应
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
@ -412,19 +519,25 @@ public class GTaskClient {
}
}
/**
*
*
* @throws NetworkFailureException
*/
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
try {
JSONObject jsPost = new JSONObject();
JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象
// 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); // 发送POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
@ -433,51 +546,64 @@ public class GTaskClient {
}
}
/**
*
*
* @param node
* @throws NetworkFailureException
*/
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// too many update items may result in an error
// set max to 10 items
// 若更新节点过多,则提交当前更新
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
mUpdateArray.put(node.getUpdateAction(getActionId()));
mUpdateArray = new JSONArray(); // 创建更新节点的数组
mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加节点更新动作
}
}
/**
*
*
* @param task
* @param preParent
* @param curParent
* @throws NetworkFailureException
*/
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
commitUpdate(); // 提交当前更新
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象
JSONArray actionList = new JSONArray(); // 动作列表
JSONObject action = new JSONObject(); // 单个动作
// action_list
// 添加移动任务的动作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and
// it is not the first one
// 如果在同一任务列表内移动且不是第一个任务则添加前置兄弟节点ID
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
if (preParent != curParent) {
// put the dest_list only if moving between tasklists
// 如果跨任务列表移动添加目标任务列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 添加客户端版本信息
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
postRequest(jsPost); // 发送POST请求
} catch (JSONException e) {
Log.e(TAG, e.toString());
@ -486,22 +612,30 @@ public class GTaskClient {
}
}
/**
*
*
* @param node
* @throws NetworkFailureException
*/
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate();
commitUpdate(); // 提交当前更新
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象
JSONArray actionList = new JSONArray(); // 动作列表
// action_list
// 添加删除节点的动作
node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 添加客户端版本信息
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
postRequest(jsPost); // 发送POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
@ -509,6 +643,14 @@ public class GTaskClient {
}
}
/**
*
*
*
* @return JSONArray JSON
* @throws NetworkFailureException
*/
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
@ -520,7 +662,7 @@ public class GTaskClient {
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the task list
// 从响应中提取任务列表
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
@ -543,10 +685,17 @@ public class GTaskClient {
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
throw new ActionFailureException("get task lists: handling json object failed");
}
}
/**
* ID
*
* @param listGid
* @return JSONArray JSON
* @throws NetworkFailureException
*/
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
try {
@ -554,7 +703,7 @@ public class GTaskClient {
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());
@ -563,23 +712,32 @@ public class GTaskClient {
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 发送请求并处理响应
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task list: handing jsonobject failed");
throw new ActionFailureException("get task list: handling json object failed");
}
}
/**
*
*
* @return Account
*/
public Account getSyncAccount() {
return mAccount;
}
/**
*
*
*/
public void resetUpdateArray() {
mUpdateArray = null;
}
}

@ -49,44 +49,64 @@ import java.util.Map;
public class GTaskManager {
// GTaskManager类的标签用于日志输出等。
private static final String TAG = GTaskManager.class.getSimpleName();
// 任务状态:成功。
public static final int STATE_SUCCESS = 0;
// 任务状态:网络错误。
public static final int STATE_NETWORK_ERROR = 1;
// 任务状态:内部错误。
public static final int STATE_INTERNAL_ERROR = 2;
// 任务状态:同步进行中。
public static final int STATE_SYNC_IN_PROGRESS = 3;
// 任务状态:同步已取消。
public static final int STATE_SYNC_CANCELLED = 4;
// GTaskManager的单例实例。
private static GTaskManager mInstance = null;
// 关联的Activity对象。
private Activity mActivity;
// 上下文对象。
private Context mContext;
// 内容解析器。
private ContentResolver mContentResolver;
// 标记是否正在同步。
private boolean mSyncing;
// 标记是否已取消同步。
private boolean mCancelled;
// 保存任务列表的HashMap键为列表ID值为任务列表对象。
private HashMap<String, TaskList> mGTaskListHashMap;
// 保存任务的HashMap键为任务ID值为任务对象。
private HashMap<String, Node> mGTaskHashMap;
// 保存元数据的HashMap键为元数据ID值为元数据对象。
private HashMap<String, MetaData> mMetaHashMap;
// 元数据列表。
private TaskList mMetaList;
// 本地删除任务ID的集合。
private HashSet<Long> mLocalDeleteIdMap;
// 保存任务全局ID到本地ID的映射的HashMap。
private HashMap<String, Long> mGidToNid;
// 保存本地ID到任务全局ID的映射的HashMap。
private HashMap<Long, String> mNidToGid;
// GTaskManager的私有构造函数初始化各种状态和映射。
private GTaskManager() {
mSyncing = false;
mCancelled = false;
@ -99,6 +119,13 @@ public class GTaskManager {
mNidToGid = new HashMap<Long, String>();
}
/**
* GTaskManager
* GTaskManager
*
* @return GTaskManager
*/
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
@ -106,11 +133,24 @@ public class GTaskManager {
return mInstance;
}
/**
*
*
*
* @param activity
*/
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
/**
*
* Google
*
* @param context
* @param asyncTask
* @return
*/
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
@ -120,6 +160,7 @@ public class GTaskManager {
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
// 清理同步相关的数据结构
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
@ -131,18 +172,18 @@ public class GTaskManager {
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// login google task
// 尝试登录 Google 任务服务
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// get the task list from google
// 初始化 Google 任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// do content sync work
// 执行内容同步工作
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
@ -156,6 +197,7 @@ public class GTaskManager {
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
// 无论成功或失败,最后都清理数据结构
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
@ -168,26 +210,36 @@ public class GTaskManager {
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
/**
* GTask
* GTaskClient
* NetworkFailureException
*
* @throws NetworkFailureException
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
if (mCancelled) // 检查是否取消了操作
return;
GTaskClient client = GTaskClient.getInstance();
GTaskClient client = GTaskClient.getInstance(); // 获取 GTask 客户端实例
try {
JSONArray jsTaskLists = client.getTaskLists();
JSONArray jsTaskLists = client.getTaskLists(); // 从客户端获取任务列表数组
// init meta list first
// 初始化元数据列表
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 寻找并初始化元数据列表
if (name
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// load meta data
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
@ -203,7 +255,7 @@ public class GTaskManager {
}
}
// create meta list if not existed
// 如果元数据列表不存在,则创建新的元数据列表
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
@ -211,12 +263,13 @@ public class GTaskManager {
GTaskClient.getInstance().createTaskList(mMetaList);
}
// init task list
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 创建并初始化除元数据之外的其他任务列表
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) {
@ -225,7 +278,7 @@ public class GTaskManager {
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// load tasks
// 加载任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
@ -241,28 +294,37 @@ public class GTaskManager {
}
}
} catch (JSONException e) {
// 处理 JSON 解析异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
throw new ActionFailureException("initGTaskList: handling JSONObject failed");
}
}
/**
*
*
* ID
*
* @throws NetworkFailureException
*/
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
mLocalDeleteIdMap.clear();
mLocalDeleteIdMap.clear(); // 清除本地删除映射表
if (mCancelled) {
return;
return; // 如果操作已被取消,则直接返回
}
// for local deleted note
// 处理本地删除的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
"(type<>? AND parent_id=?)", new String[]{
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
@ -270,29 +332,29 @@ public class GTaskManager {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
mGTaskHashMap.remove(gid); // 从映射表中移除
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行内容同步
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加到本地删除映射表
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close();
c.close(); // 关闭游标
c = null;
}
}
// sync folder first
// 首先同步文件夹信息
syncFolder();
// for note existing in database
// 处理数据库中存在的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
"(type=? AND parent_id<>?)", new String[]{
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
@ -300,20 +362,20 @@ public class GTaskManager {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mGTaskHashMap.remove(gid); // 从映射表中移除
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 更新ID映射
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
syncType = node.getSyncAction(c); // 获取同步动作
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
// 如果没有GTask ID则视为本地新增
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
// 如果有GTask ID但本地不存在则视为远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
doContentSync(syncType, node, c); // 执行内容同步
}
} else {
Log.w(TAG, "failed to query existing note in database");
@ -321,36 +383,42 @@ public class GTaskManager {
} finally {
if (c != null) {
c.close();
c.close(); // 关闭游标
c = null;
}
}
// go through remaining items
// 处理剩余项目
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 将剩余项目作为本地新增处理
}
// mCancelled can be set by another thread, so we neet to check one by
// one
// clear local delete table
// 检查是否取消操作清理本地删除表并更新本地同步ID
if (!mCancelled) {
// 批量删除本地已删除的笔记,如果失败则抛出异常
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// refresh local sync id
// 刷新本地同步ID
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
GTaskClient.getInstance().commitUpdate(); // 提交更新
refreshLocalSyncId(); // 刷新本地同步ID
}
}
/**
*
*
*
*
* @throws NetworkFailureException
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
@ -361,7 +429,7 @@ public class GTaskManager {
return;
}
// for root folder
// 同步根文件夹
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
@ -369,11 +437,12 @@ public class GTaskManager {
c.moveToNext();
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
// 判断节点是否为空,不为空则更新,为空则添加
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// for system folder, only update remote name if necessary
// 仅当系统文件夹的名称需要更新时执行内容同步
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
@ -390,22 +459,22 @@ public class GTaskManager {
}
}
// for call-note folder
// 同步通话记录文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
new String[]{
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
// 判断节点是否为空,不为空则更新,为空则添加
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// for system folder, only update remote name if
// necessary
// 仅当系统文件夹的名称需要更新时执行内容同步
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
@ -424,16 +493,17 @@ public class GTaskManager {
}
}
// for local existing folders
// 同步本地已存在的文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
"(type=? AND parent_id<>?)", new String[]{
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
// 判断节点是否为空,不为空则更新,为空则根据情况添加或删除
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
@ -441,10 +511,10 @@ public class GTaskManager {
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
@ -460,7 +530,7 @@ public class GTaskManager {
}
}
// for remote add folders
// 同步远程添加的文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
@ -476,59 +546,78 @@ public class GTaskManager {
GTaskClient.getInstance().commitUpdate();
}
/**
*
*
* @param syncType
* @param node
* @param c 使
* @throws NetworkFailureException
*/
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
if (mCancelled) { // 检查是否已取消同步操作
return;
}
MetaData meta;
switch (syncType) {
case Node.SYNC_ACTION_ADD_LOCAL:
case Node.SYNC_ACTION_ADD_LOCAL: // 添加本地节点
addLocalNode(node);
break;
case Node.SYNC_ACTION_ADD_REMOTE:
case Node.SYNC_ACTION_ADD_REMOTE: // 添加远程节点
addRemoteNode(node, c);
break;
case Node.SYNC_ACTION_DEL_LOCAL:
case Node.SYNC_ACTION_DEL_LOCAL: // 删除本地节点
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
GTaskClient.getInstance().deleteNode(meta); // 从服务器删除节点
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 记录已删除的本地节点ID
break;
case Node.SYNC_ACTION_DEL_REMOTE:
case Node.SYNC_ACTION_DEL_REMOTE: // 删除远程节点
meta = mMetaHashMap.get(node.getGid());
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
GTaskClient.getInstance().deleteNode(meta); // 从服务器删除节点
}
GTaskClient.getInstance().deleteNode(node);
GTaskClient.getInstance().deleteNode(node); // 直接从本地数据库删除节点
break;
case Node.SYNC_ACTION_UPDATE_LOCAL:
case Node.SYNC_ACTION_UPDATE_LOCAL: // 更新本地节点
updateLocalNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_REMOTE:
case Node.SYNC_ACTION_UPDATE_REMOTE: // 更新远程节点
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// merging both modifications maybe a good idea
// right now just use local update simply
case Node.SYNC_ACTION_UPDATE_CONFLICT: // 处理更新冲突
// 目前简单地采用本地更新,未来可能需要合并双方修改
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
case Node.SYNC_ACTION_NONE: // 无操作
break;
case Node.SYNC_ACTION_ERROR:
case Node.SYNC_ACTION_ERROR: // 默认错误处理
default:
throw new ActionFailureException("unkown sync action type");
throw new ActionFailureException("unkown sync action type"); // 抛出未知同步操作类型的异常
}
}
/**
*
* SqlNote
* SqlNote
* JSON ID ID
* SqlNote ID
*
* @param node null
* @throws NetworkFailureException
*/
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
return;
return; // 如果操作已取消,则直接返回,不执行添加操作
}
SqlNote sqlNote;
if (node instanceof TaskList) {
// 处理任务列表节点,根据节点名称设置特殊的 SqlNote 属性
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
@ -541,15 +630,17 @@ public class GTaskManager {
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
// 处理任务节点,从节点内容创建 JSON 对象,并根据需要调整 ID 字段
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
// 检查并处理 JSON 对象中的 Note 和 Data ID 字段
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// the id is not available, have to create a new one
// 如果笔记 ID 已存在,则移除该 ID
note.remove(NoteColumns.ID);
}
}
@ -562,13 +653,11 @@ public class GTaskManager {
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// the data id is not available, have to create
// a new one
// 如果数据 ID 已存在,则移除该 ID
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
@ -576,6 +665,7 @@ public class GTaskManager {
}
sqlNote.setContent(js);
// 设置节点的父 ID
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
@ -584,28 +674,36 @@ public class GTaskManager {
sqlNote.setParentId(parentId.longValue());
}
// create the local node
// 提交 SqlNote 到数据库,并更新 ID 映射关系
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// update meta
// 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
*
* @param node
* @param c
* @throws NetworkFailureException
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// update the note locally
// 根据节点内容更新本地数据库中的笔记
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
// 确定父节点ID任务则查找父任务ID否则默认为根文件夹ID
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
if (parentId == null) {
@ -615,10 +713,17 @@ public class GTaskManager {
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// update meta info
// 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
*
* @param node
* @param c
* @throws NetworkFailureException
*/
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
@ -627,11 +732,12 @@ public class GTaskManager {
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// update remotely
// 如果是任务类型,则远程创建任务;否则,根据条件判断是否需要创建新的任务列表
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
// 查找任务所属的任务列表ID
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
@ -642,12 +748,12 @@ public class GTaskManager {
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// add meta
// 更新远程元数据
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// we need to skip folder if it has already existed
// 判断是否需要创建新的任务列表
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT;
@ -656,6 +762,7 @@ public class GTaskManager {
else
folderName += sqlNote.getSnippet();
// 在已有的任务列表中查找匹配的条目
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
@ -671,7 +778,7 @@ public class GTaskManager {
}
}
// no match we can add now
// 如果没有找到匹配的任务列表,则创建新的任务列表
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
@ -681,43 +788,52 @@ public class GTaskManager {
n = (Node) tasklist;
}
// update local note
// 更新本地数据库中的笔记信息
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id mapping
// 更新GID和ID的映射关系
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
/**
*
*
* @param node
* @param c
* @throws NetworkFailureException
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
if (mCancelled) { // 检查是否已取消操作
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
SqlNote sqlNote = new SqlNote(mContext, c); // 从数据库游标中创建 SqlNote 对象
// update remotely
// 使用本地 JSON 格式更新远程节点内容
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
GTaskClient.getInstance().addUpdateNode(node); // 将节点添加到更新队列
// update meta
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote);
// move task if necessary
// 如果是笔记类型,检查并移动任务
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent();
TaskList preParentList = task.getParent(); // 获取任务的当前父任务列表
String curParentGid = mNidToGid.get(sqlNote.getParentId());
String curParentGid = mNidToGid.get(sqlNote.getParentId()); // 获取当前父任务列表的 GID
if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 获取当前父任务列表对象
// 如果任务的父任务列表发生变化,则移动任务
if (preParentList != curParentList) {
preParentList.removeChildTask(task);
curParentList.addChildTask(task);
@ -725,18 +841,27 @@ public class GTaskManager {
}
}
// clear local modified flag
// 重置本地修改标志,并提交更改
sqlNote.resetLocalModified();
sqlNote.commit(true);
}
/**
*
*
* @param gid
* @param sqlNote SqlNote
* @throws NetworkFailureException
*/
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid);
if (sqlNote != null && sqlNote.isNoteType()) { // 确保是笔记类型
MetaData metaData = mMetaHashMap.get(gid); // 尝试获取现有的元数据对象
if (metaData != null) {
// 更新元数据内容并加入更新队列
metaData.setMeta(gid, sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
// 如果元数据不存在,则创建新的元数据对象并添加到远程
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
@ -746,12 +871,21 @@ public class GTaskManager {
}
}
/**
* ID
* gtaskID
* gtask IDActionFailureException
*
* @throws NetworkFailureException
* @throws ActionFailureException gtask ID
*/
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// get the latest gtask list
// 清空现有的gtask列表和元数据准备获取最新的数据
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
@ -759,8 +893,9 @@ public class GTaskManager {
Cursor c = null;
try {
// 查询本地笔记内容准备更新同步ID
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] {
"(type<>? AND parent_id<>?)", new String[]{
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
@ -774,15 +909,18 @@ public class GTaskManager {
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
// 如果查询到的笔记没有对应的gtask ID则抛出异常
Log.e(TAG, "something is missed");
throw new ActionFailureException(
"some local items don't have gid after sync");
}
}
} else {
// 如果查询操作失败,记录警告信息
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
// 释放Cursor资源
if (c != null) {
c.close();
c = null;
@ -790,11 +928,21 @@ public class GTaskManager {
}
}
/**
*
*
* @return
*/
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
/**
*
*
*/
public void cancelSync() {
mCancelled = true;
}
}

@ -1,19 +1,6 @@
/*
* 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.
* GTaskSyncServiceGoogle
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
@ -24,28 +11,42 @@ import android.os.Bundle;
import android.os.IBinder;
public class GTaskSyncService extends Service {
// 同步操作的类型
public final static String ACTION_STRING_NAME = "sync_action_type";
// 启动同步
public final static int ACTION_START_SYNC = 0;
// 取消同步
public final static int ACTION_CANCEL_SYNC = 1;
// 无效操作
public final static int ACTION_INVALID = 2;
// 服务广播的名称
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
// 广播中是否正在同步的标志
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
// 广播中的同步进度消息
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
// 静态变量用于存储当前同步任务实例
private static GTaskASyncTask mSyncTask = null;
// 存储同步进度的字符串
private static String mSyncProgress = "";
/*
*
*
*/
private void startSync() {
if (mSyncTask == null) {
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
public void onComplete() {
// 同步任务完成时的处理:重置静态变量,发送广播,停止服务
mSyncTask = null;
sendBroadcast("");
stopSelf();
@ -56,17 +57,27 @@ public class GTaskSyncService extends Service {
}
}
/*
*
*/
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
/*
* null
*/
@Override
public void onCreate() {
mSyncTask = null;
}
/*
*
*
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
@ -86,6 +97,9 @@ public class GTaskSyncService extends Service {
return super.onStartCommand(intent, flags, startId);
}
/*
*
*/
@Override
public void onLowMemory() {
if (mSyncTask != null) {
@ -93,10 +107,15 @@ public class GTaskSyncService extends Service {
}
}
// 服务绑定时返回null此服务不提供绑定功能
public IBinder onBind(Intent intent) {
return null;
}
/*
* 广
* 广
*/
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
@ -105,6 +124,10 @@ public class GTaskSyncService extends Service {
sendBroadcast(intent);
}
/*
* Activity
*
*/
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
@ -112,16 +135,28 @@ public class GTaskSyncService extends Service {
activity.startService(intent);
}
/*
* Context
*
*/
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
/*
*
*
*/
public static boolean isSyncing() {
return mSyncTask != null;
}
/*
*
*
*/
public static String getProgressString() {
return mSyncProgress;
}

@ -15,6 +15,7 @@
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
@ -38,16 +39,20 @@ public class Note {
private ContentValues mNoteDiffValues;
private NoteData mNoteData;
private static final String TAG = "Note";
/**
* Create a new note id for adding a new note to databases
* ID
*
* @param context 访
* @param folderId ID
* @return ID
*/
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.TOP,"1");
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.PARENT_ID, folderId);
@ -57,11 +62,11 @@ public class Note {
try {
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
Log.e(TAG, "获取笔记ID错误 :" + e.toString());
noteId = 0;
}
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
throw new IllegalStateException("错误的笔记ID:" + noteId);
}
return noteId;
}
@ -71,59 +76,96 @@ public class Note {
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());
}
public void setTopValue(String key, String value) {
mNoteDiffValues.put(key, value);
}
/**
*
*
* @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 truefalse
*/
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
*
*
* @param context 访
* @param noteId ID
* @return truefalse
*/
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
throw new IllegalArgumentException("错误的笔记ID:" + noteId);
}
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
// 理论上,一旦数据改变,笔记应该在本地修改标记和修改日期上更新。为了数据安全,即使更新笔记失败,我们也更新笔记的数据信息
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
Log.e(TAG, "更新笔记错误,不应该发生");
// 不返回,继续执行
}
mNoteDiffValues.clear();
@ -135,13 +177,14 @@ public class Note {
return true;
}
/**
* NoteData
*/
private class NoteData {
private long mTextDataId;
private ContentValues mTextDataValues;
private ContentValues mTopValues;
private long mCallDataId;
private ContentValues mCallDataValues;
@ -151,53 +194,86 @@ public class Note {
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTopValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
/**
*
*
* @return truefalse
*/
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");
if (id <= 0) {
throw new IllegalArgumentException("文本数据ID应该大于0");
}
mTextDataId = id;
}
/**
* ID
*
* @param id ID
*/
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
throw new IllegalArgumentException("通话数据ID应该大于0");
}
mCallDataId = id;
}
/**
*
*
* @param key
* @param value
*/
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
*
*
* @param key
* @param value
*/
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
*
*
* @param context 访
* @param noteId ID
* @return Urinull
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
throw new IllegalArgumentException("错误的笔记ID:" + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {
if (mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
@ -206,7 +282,7 @@ public class Note {
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
Log.e(TAG, "插入新的文本数据失败笔记ID" + noteId);
mTextDataValues.clear();
return null;
}
@ -219,7 +295,7 @@ public class Note {
mTextDataValues.clear();
}
if(mCallDataValues.size() > 0) {
if (mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
@ -228,7 +304,7 @@ public class Note {
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
Log.e(TAG, "插入新的通话数据失败笔记ID" + noteId);
mCallDataValues.clear();
return null;
}
@ -259,3 +335,4 @@ public class Note {
}
}
}

@ -29,49 +29,45 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.data.NotesProvider;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
// WorkingNote类用于管理笔记的相关信息
public class WorkingNote {
// Note for the working note
// 笔记对象,包含笔记的详细信息
private Note mNote;
// Note Id
// 笔记的唯一标识符
private long mNoteId;
// Note content
// 笔记的内容
private String mContent;
// Note mode
// 笔记的模式,例如普通、草稿等
private int mMode;
// 设置提醒日期的时间戳
private long mAlertDate;
// 笔记最后修改日期的时间戳
private long mModifiedDate;
// 笔记背景颜色的资源ID
private int mBgColorId;
// 小部件的ID
private int mWidgetId;
// 小部件的类型
private int mWidgetType;
// 笔记所属文件夹的ID
private long mFolderId;
// 上下文对象,用于访问应用的环境信息
private Context mContext;
// 日志标签用于Log输出
private static final String TAG = "WorkingNote";
// 标记笔记是否被删除
private boolean mIsDeleted;
/**记录已置顶便签*/
private String mTop = "0";
/**记录便签所属类别*/
private int mClass;
// 笔记设置变化监听器
private NoteSettingChangedListener mNoteSettingStatusListener;
public static final String[] DATA_PROJECTION = new String[] {
// 定义一个静态数组,用于在查询时投影数据列
public static final String[] DATA_PROJECTION = new String[]{
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
@ -81,39 +77,53 @@ public class WorkingNote {
DataColumns.DATA4,
};
public static final String[] NOTE_PROJECTION = new String[] {
// 定义查询Note表时需要投影的列
public static final String[] NOTE_PROJECTION = new String[]{
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE,
NoteColumns.TOP,
NoteColumns.MODIFIED_DATE
};
// 数据ID列的索引
private static final int DATA_ID_COLUMN = 0;
// 数据内容列的索引
private static final int DATA_CONTENT_COLUMN = 1;
// 数据MIME类型列的索引
private static final int DATA_MIME_TYPE_COLUMN = 2;
// 数据模式列的索引
private static final int DATA_MODE_COLUMN = 3;
// Note表中父ID列的索引
private static final int NOTE_PARENT_ID_COLUMN = 0;
// Note表中提醒日期列的索引
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
// Note表中背景颜色ID列的索引
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
// Note表中Widget ID列的索引
private static final int NOTE_WIDGET_ID_COLUMN = 3;
// Note表中Widget类型列的索引
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
// Note表中修改日期列的索引
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
private static final int NOTE_TOP_COLUMN = 6;
// New note construct
/**
*
*
* @param context 访
* @param folderId ID
*/
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
@ -124,10 +134,15 @@ public class WorkingNote {
mIsDeleted = false;
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mTop = String.valueOf(getTopId());
}
// Existing note construct
/**
*
*
* @param context 访
* @param noteId ID
* @param folderId ID
*/
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
@ -137,57 +152,91 @@ public class WorkingNote {
loadNote();
}
/**
*
* ID
*
*/
private void loadNote() {
// 查询指定ID的笔记信息
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
if (cursor != null) {
// 如果查询结果不为空,尝试读取数据
if (cursor.moveToFirst()) {
// 从查询结果中获取笔记的各个属性
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
mTop = cursor.getString(NOTE_TOP_COLUMN);
}
// 关闭查询结果集
cursor.close();
} else {
// 如果查询结果为空,记录错误并抛出异常
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
// 加载笔记的附加数据,如内容、设置等
loadNoteData();
}
/**
*
* ID
*
*/
private void loadNoteData() {
// 查询指定笔记ID的附加数据
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
DataColumns.NOTE_ID + "=?", new String[]{
String.valueOf(mNoteId)
}, null);
if (cursor != null) {
// 如果查询结果不为空,尝试读取数据
if (cursor.moveToFirst()) {
do {
// 根据数据类型处理不同的笔记内容
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) {
// 处理普通笔记内容
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 处理通话笔记内容
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
// 记录错误的笔记类型
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
// 关闭查询结果集
cursor.close();
} else {
// 如果查询结果为空,记录错误并抛出异常
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
/**
*
*
* @param context 访
* @param folderId ID
* @param widgetId ID
* @param widgetType
* @param defaultBgColorId ID
* @return
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
@ -197,24 +246,40 @@ public class WorkingNote {
return note;
}
/**
* ID
*
* @param context 访
* @param id ID
* @return ID
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
/**
*
*
*
*
* @return truefalse
*/
public synchronized boolean saveNote() {
// 判断是否值得保存该笔记
if (isWorthSaving()) {
// 检查数据库中是否已存在该笔记
if (!existInDatabase()) {
// 为笔记生成新的ID
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
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) {
@ -226,11 +291,23 @@ public class WorkingNote {
}
}
/**
*
*
* @return ID0truefalse
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
*
*
*
* @return truefalse
*/
private boolean isWorthSaving() {
// 判断笔记是否值得保存
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
@ -239,21 +316,23 @@ public class WorkingNote {
}
}
/**
*
*
* @param l
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
public void setTop(String Top){
if (!mTop.equals(Top)) {
mTop = Top;
mNote.setTopValue(NoteColumns.TOP,mTop);
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onTopChanged(Top);
}
}
/**
*
*
* @param date
* @param set
*/
public void setAlertDate(long date, boolean set) {
// 更新提醒日期并触发监听器
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
@ -263,15 +342,27 @@ 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) {
// 更新背景颜色ID并触发监听器
if (id != mBgColorId) {
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
@ -281,130 +372,217 @@ public class WorkingNote {
}
}
/**
*
*
* @param mode
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
// 当前模式与新模式不同时,通知监听器模式发生变化
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
// 更新笔记中的模式值
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
/**
*
*
* @param type
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
// 更新笔记中小部件类型的值
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
/**
* ID
*
* @param id ID
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
// 更新笔记中小部件ID的值
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
/**
*
*
* @param text
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
// 更新笔记中的文本内容
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
public void setmContent(String text){
mContent = text;
}
/**
*
*
* @param phoneNumber
* @param callDate
*/
public void convertToCallNote(String phoneNumber, long callDate) {
// 设置通话日期和电话号码,并关联至通话记录文件夹
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
/**
*
*
* @return truefalse
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
public int getTopId() {
if (mTop.equals("1")) {
return 1;
} else {
return 0;
}
}
/**
*
*
* @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 {
public void setmContent(String text){//qxq:一个简单的文本信息传递
mContent = text;
}
/**
* Called when the background color of current note has just changed
*
*/
void onBackgroundColorChanged();
public interface NoteSettingChangedListener {
/**
* Called when user set top
*
*/
void onTopChanged(String Top);
void onBackgroundColorChanged();
/**
* Called when user set clock
*
*
* @param date
* @param set
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*
*
* @param oldMode
* @param newMode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -38,9 +38,15 @@ import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
// 单例模式相关变量
private static BackupUtils sInstance;
/**
* BackupUtils
*
* @param context 访
* @return BackupUtils
*/
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
@ -49,43 +55,71 @@ public class BackupUtils {
}
/**
* Following states are signs to represents backup or restore
* status
*
*/
// Currently, the sdcard is not mounted
// 当前SD卡未挂载
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;
/**
* BackupUtils
*
* @param context
*/
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
/**
*
*
* @return truefalse
*/
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
*
*
* @return STATE_*
*/
public int exportToText() {
return mTextExport.exportToText();
}
/**
*
*
* @return
*/
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
/**
*
*
* @return
*/
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
/**
* TextExport
*/
private static class TextExport {
// 查询笔记时需要的列
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
@ -93,12 +127,12 @@ public class BackupUtils {
NoteColumns.TYPE
};
// 笔记列的索引
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
// 查询数据时需要的列
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
@ -108,52 +142,79 @@ public class BackupUtils {
DataColumns.DATA4,
};
// 定义数据列的内容索引
private static final int DATA_COLUMN_CONTENT = 0;
// 定义数据列的MIME类型索引
private static final int DATA_COLUMN_MIME_TYPE = 1;
// 定义数据列的呼叫日期索引
private static final int DATA_COLUMN_CALL_DATE = 2;
// 定义数据列的电话号码索引
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
private final String [] TEXT_FORMAT;
// 用于导出笔记的文本格式数组
private final String[] TEXT_FORMAT;
// 定义文本格式的索引:文件夹名称
private static final int FORMAT_FOLDER_NAME = 0;
// 定义文本格式的索引:笔记日期
private static final int FORMAT_NOTE_DATE = 1;
// 定义文本格式的索引:笔记内容
private static final int FORMAT_NOTE_CONTENT = 2;
// 上下文对象,用于访问资源和内容解析器
private Context mContext;
// 文件名
private String mFileName;
// 文件目录
private String mFileDirectory;
/**
*
*
* @param context ActivityApplication
*/
public TextExport(Context context) {
// 初始化文本格式数组
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
/**
*
*
* @param id ID
* @return
*/
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
*
*
* @param folderId ID
* @param ps
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
// 查询属于该文件夹的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[]{
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
// 打印笔记的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
// 导出该笔记的内容到文本
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
@ -162,12 +223,17 @@ public class BackupUtils {
}
}
/**
* Export note identified by id to a print stream
* id
*
* @param noteId id
* @param ps
*/
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询指定id的笔记数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[]{
noteId
}, null);
@ -176,25 +242,25 @@ public class BackupUtils {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
// 处理通话记录类型的笔记
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
// 打印电话号码、通话时间、附件位置
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
// 处理普通笔记类型
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
@ -205,9 +271,9 @@ public class BackupUtils {
}
dataCursor.close();
}
// print a line separator between note
// 在每个笔记内容之间打印一行分隔符
try {
ps.write(new byte[] {
ps.write(new byte[]{
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
@ -216,20 +282,25 @@ public class BackupUtils {
}
/**
* Note will be exported as text which is user readable
*
*
* @return STATE_SUCCESS
*/
public int exportToText() {
// 检查外部存储器是否可用
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
// 获取用于导出的打印流
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
// 首先导出文件夹及其包含的笔记
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
@ -240,9 +311,9 @@ public class BackupUtils {
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
// 打印文件夹名称
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
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);
@ -251,13 +322,14 @@ public class BackupUtils {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
// 导出文件夹中的笔记
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// Export notes in root's folder
// 导出根文件夹中的笔记
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
@ -267,10 +339,11 @@ public class BackupUtils {
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
// 打印笔记的修改时间
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
// 导出笔记内容
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
@ -282,51 +355,72 @@ public class BackupUtils {
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
* {@generateExportedTextFile}
* SDPrintStream
* null
*
* @return PrintStream null
*/
private PrintStream getExportToTextPrintStream() {
// 生成文件
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
Log.e(TAG, "create file to exported failed"); // 文件创建失败
return null;
}
// 更新文件名和文件目录信息
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
FileOutputStream fos = new FileOutputStream(file); // 创建文件输出流
ps = new PrintStream(fos); // 将文件输出流包装成PrintStream
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
return null; // 文件未找到异常返回null
} catch (NullPointerException e) {
e.printStackTrace();
return null;
return null; // 空指针异常返回null
}
return ps;
return ps; // 返回PrintStream对象
}
}
/**
* Generate the text file to store imported data
* SD
*
* @param context 访
* @param filePathResId ID
* @param fileNameFormatResId ID
* @return null
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
// 拼接SD卡的根目录
sb.append(Environment.getExternalStorageDirectory());
// 拼接从资源id获取的路径字符串
sb.append(context.getString(filePathResId));
// 创建文件目录对象
File filedir = new File(sb.toString());
// 拼接文件名,文件名包含日期信息
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
// 根据拼接的路径创建文件对象
File file = new File(sb.toString());
try {
// 如果文件目录不存在,则创建文件目录
if (!filedir.exists()) {
filedir.mkdir();
}
// 如果文件不存在,则创建新文件
if (!file.exists()) {
file.createNewFile();
}
@ -337,8 +431,10 @@ public class BackupUtils {
e.printStackTrace();
}
// 如果遇到异常返回null
return null;
}
}

@ -16,7 +16,6 @@
package net.micode.notes.tool;
import android.app.Activity;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
@ -26,27 +25,26 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import android.database.sqlite.SQLiteDatabase;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper;
import net.micode.notes.data.NotesProvider;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.model.Note;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.TreeSet;
public class DataUtils {
public static final String TAG = "DataUtils";
/**
*
*
* @param resolver
* @param ids ID
* @return nulltruefalse
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
@ -57,9 +55,10 @@ public class DataUtils {
return true;
}
// 构建删除操作的列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
if (id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
@ -69,6 +68,7 @@ public class DataUtils {
}
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;
@ -82,6 +82,14 @@ public class DataUtils {
return false;
}
/**
*
*
* @param resolver
* @param id ID
* @param srcFolderId ID
* @param desFolderId ID
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
@ -90,6 +98,14 @@ public class DataUtils {
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
/**
*
*
* @param resolver
* @param ids ID
* @param folderId ID
* @return nulltruefalse
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
@ -97,6 +113,7 @@ public class DataUtils {
return true;
}
// 构建更新操作的列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
@ -108,8 +125,9 @@ public class DataUtils {
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());
Log.d(TAG, "move notes failed, ids:" + ids.toString());
return false;
}
return true;
@ -122,18 +140,21 @@ public class DataUtils {
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*
*
* @param resolver
* @return
*/
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
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)},
new String[]{String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) {
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
@ -146,11 +167,19 @@ public class DataUtils {
return count;
}
/**
*
*
* @param resolver
* @param noteId ID
* @param type
* @return truefalse
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
new String[]{String.valueOf(type)},
null);
boolean exist = false;
@ -163,6 +192,13 @@ public class DataUtils {
return exist;
}
/**
* ID
*
* @param resolver
* @param noteId ID
* @return truefalse
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
@ -177,6 +213,13 @@ public class DataUtils {
return exist;
}
/**
* ID
*
* @param resolver
* @param dataId ID
* @return truefalse
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
@ -191,15 +234,22 @@ public class DataUtils {
return exist;
}
/**
*
*
* @param resolver
* @param name
* @return truefalse
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
new String[]{name}, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) {
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
@ -207,11 +257,18 @@ public class DataUtils {
return exist;
}
/**
*
*
* @param resolver
* @param folderId ID
* @return
*/
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
new String[]{NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE},
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
new String[]{String.valueOf(folderId)},
null);
HashSet<AppWidgetAttribute> set = null;
@ -234,11 +291,18 @@ public class DataUtils {
return set;
}
/**
* ID
*
* @param resolver
* @param noteId ID
* @return
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
new String[]{CallNote.PHONE_NUMBER},
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
new String[]{String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE},
null);
if (cursor != null && cursor.moveToFirst()) {
@ -253,12 +317,20 @@ public class DataUtils {
return "";
}
/**
* ID
*
* @param resolver
* @param phoneNumber
* @param callDate
* @return ID0
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
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 },
new String[]{String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber},
null);
if (cursor != null) {
@ -274,30 +346,49 @@ public class DataUtils {
return 0;
}
/**
* ID
*
* @param resolver
* @param noteId ID
* @return IllegalArgumentException
*/
public static String getSnippetById(ContentResolver resolver, long noteId) {
// 使用内容解析器查询特定ID的笔记的摘要
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
new String[]{NoteColumns.SNIPPET},
NoteColumns.ID + "=?",
new String [] { String.valueOf(noteId)},
new String[]{String.valueOf(noteId)},
null);
if (cursor != null) {
String snippet = "";
// 如果查询结果不为空,尝试获取摘要
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
// 关闭游标
cursor.close();
return snippet;
}
// 如果找不到指定ID的笔记抛出异常
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
/**
*
*
*
* @param snippet
* @return
*/
public static String getFormattedSnippet(String snippet) {
// 如果摘要字符串不为空,进行格式化处理
if (snippet != null) {
snippet = snippet.trim();
int index = snippet.indexOf('\n');
snippet = snippet.trim(); // 去除两端的空白字符
int index = snippet.indexOf('\n'); // 查找第一个换行符的位置
if (index != -1) {
snippet = snippet.substring(0, index);
snippet = snippet.substring(0, index); // 截取至第一个换行符之前
}
}
return snippet;

@ -1,113 +1,60 @@
/*
* 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.
* GTaskStringUtils
* GTaskGTaskJSON
*/
package net.micode.notes.tool;
public class GTaskStringUtils {
public final static String GTASK_JSON_ACTION_ID = "action_id";
public final static String GTASK_JSON_ACTION_LIST = "action_list";
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
public final static String GTASK_JSON_COMPLETED = "completed";
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
public final static String GTASK_JSON_DELETED = "deleted";
public final static String GTASK_JSON_DEST_LIST = "dest_list";
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
public final static String GTASK_JSON_ID = "id";
public final static String GTASK_JSON_INDEX = "index";
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
public final static String GTASK_JSON_LIST_ID = "list_id";
public final static String GTASK_JSON_LISTS = "lists";
public final static String GTASK_JSON_NAME = "name";
public final static String GTASK_JSON_NEW_ID = "new_id";
public final static String GTASK_JSON_NOTES = "notes";
public final static String GTASK_JSON_PARENT_ID = "parent_id";
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
public final static String GTASK_JSON_RESULTS = "results";
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
public final static String GTASK_JSON_TASKS = "tasks";
public final static String GTASK_JSON_TYPE = "type";
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
public final static String GTASK_JSON_TYPE_TASK = "TASK";
public final static String GTASK_JSON_USER = "user";
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
public final static String FOLDER_DEFAULT = "Default";
public final static String FOLDER_CALL_NOTE = "Call_Note";
public final static String FOLDER_META = "METADATA";
public final static String META_HEAD_GTASK_ID = "meta_gid";
public final static String META_HEAD_NOTE = "meta_note";
public final static String META_HEAD_DATA = "meta_data";
// GTask 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_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_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"; // 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"; // 列表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"; // 新ID
public final static String GTASK_JSON_NOTES = "notes"; // 备注
public final static String GTASK_JSON_PARENT_ID = "parent_id"; // 父ID
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_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"; // 用户
// 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";
}

@ -1,17 +1,5 @@
/*
* 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.
* ResourceParser
*/
package net.micode.notes.tool;
@ -24,23 +12,31 @@ import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
// 定义笔记背景颜色的常量
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
// 默认背景颜色
public static final int BG_DEFAULT_COLOR = YELLOW;
// 定义文本大小的常量
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
// 默认字体大小
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
/**
*
*/
public static class NoteBgResources {
private final static int [] BG_EDIT_RESOURCES = new int [] {
// 编辑状态下的背景资源数组
private final static int[] BG_EDIT_RESOURCES = new int[]{
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
@ -48,7 +44,8 @@ public class ResourceParser {
R.drawable.edit_red
};
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
// 编辑状态下的标题背景资源数组
private final static int[] BG_EDIT_TITLE_RESOURCES = new int[]{
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
@ -56,15 +53,23 @@ public class ResourceParser {
R.drawable.edit_title_red
};
// 根据id获取编辑状态下的背景资源
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
// 根据id获取编辑状态下的标题背景资源
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
/**
* id
*
* @param context 访SharedPreferences
* @return idid
*/
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
@ -74,8 +79,12 @@ public class ResourceParser {
}
}
/**
*
*/
public static class NoteItemBgResources {
private final static int [] BG_FIRST_RESOURCES = new int [] {
// 第一个列表项的背景资源数组
private final static int[] BG_FIRST_RESOURCES = new int[]{
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
@ -83,7 +92,8 @@ public class ResourceParser {
R.drawable.list_red_up
};
private final static int [] BG_NORMAL_RESOURCES = new int [] {
// 普通列表项的背景资源数组
private final static int[] BG_NORMAL_RESOURCES = new int[]{
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
@ -91,7 +101,8 @@ public class ResourceParser {
R.drawable.list_red_middle
};
private final static int [] BG_LAST_RESOURCES = new int [] {
// 最后一个列表项的背景资源数组
private final static int[] BG_LAST_RESOURCES = new int[]{
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
@ -99,7 +110,8 @@ public class ResourceParser {
R.drawable.list_red_down,
};
private final static int [] BG_SINGLE_RESOURCES = new int [] {
// 单个列表项的背景资源数组
private final static int[] BG_SINGLE_RESOURCES = new int[]{
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
@ -107,29 +119,38 @@ public class ResourceParser {
R.drawable.list_red_single
};
// 获取第一个列表项的背景资源
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
// 获取最后一个列表项的背景资源
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
// 获取单个列表项的背景资源
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
// 获取普通列表项的背景资源
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
// 获取文件夹背景资源
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
/**
*
*/
public static class WidgetBgResources {
private final static int [] BG_2X_RESOURCES = new int [] {
// 2x 小部件背景资源数组
private final static int[] BG_2X_RESOURCES = new int[]{
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
@ -137,11 +158,13 @@ public class ResourceParser {
R.drawable.widget_2x_red,
};
// 根据id获取2x小部件的背景资源
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
private final static int [] BG_4X_RESOURCES = new int [] {
// 4x 小部件背景资源数组
private final static int[] BG_4X_RESOURCES = new int[]{
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
@ -149,33 +172,37 @@ public class ResourceParser {
R.drawable.widget_4x_red
};
// 根据id获取4x小部件的背景资源
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
/**
*
*/
public static class TextAppearanceResources {
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
// 文本外观资源数组
private final static int[] TEXTAPPEARANCE_RESOURCES = new int[]{
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
// 根据id获取文本外观资源
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
// 如果id超出资源数组范围返回默认字体大小
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
// 获取文本外观资源的数量
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

@ -1,4 +1,3 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
@ -41,20 +40,33 @@ import net.micode.notes.tool.DataUtils;
import java.io.IOException;
/*
* AlarmAlertActivity
*
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
// 笔记的ID
private long mNoteId;
// 笔记内容的简短预览
private String mSnippet;
// 预览文本的最大长度
private static final int SNIPPET_PREW_MAX_LEN = 60;
// 用于播放提醒声音的MediaPlayer对象
MediaPlayer mPlayer;
/*
* onCreate IntentID
*
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 请求无标题的窗口
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 设置窗口在锁屏时也显示,并根据屏幕状态决定是否保持唤醒
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
@ -62,8 +74,8 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 从Intent中获取笔记ID和简短内容
Intent intent = getIntent();
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
@ -75,6 +87,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
return;
}
// 如果笔记在数据库中可见,则显示动作对话框并播放声音,否则结束活动
mPlayer = new MediaPlayer();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
@ -84,14 +97,24 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
}
/*
*
*
* @return truefalse
*/
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
/*
*
*
*/
private void playAlarmSound() {
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 检查是否在静音模式下影响报警声音
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
@ -106,20 +129,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
*
*
*/
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
@ -131,24 +154,38 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
dialog.show().setOnDismissListener(this);
}
/*
*
*
*/
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
// 如果点击的是“进入”按钮,则启动笔记编辑活动
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
// 关闭活动
break;
}
}
/*
*
*
*/
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
/*
*
* MediaPlayer
*/
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();

@ -1,19 +1,7 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 广
*
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
@ -30,36 +18,54 @@ import net.micode.notes.data.Notes.NoteColumns;
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
// 查询笔记时需要的列
private static final String[] PROJECTION = new String[]{
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
// 列的索引
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
/**
* 广
*
* @param context 访
* @param intent 广
*/
@Override
public void onReceive(Context context, Intent intent) {
// 获取当前日期和时间
long currentDate = System.currentTimeMillis();
// 查询数据库中所有需要提醒的笔记
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
new String[]{String.valueOf(currentDate)},
null);
if (c != null) {
// 遍历查询结果,为每个需要提醒的笔记设置提醒
if (c.moveToFirst()) {
do {
// 获取提醒日期
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建Intent用于在提醒时间触发AlarmReceiver
Intent sender = new Intent(context, AlarmReceiver.class);
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建PendingIntent它是一个延迟的意图可以在特定时间由系统触发
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取AlarmManager服务用于设置提醒
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
// 设置提醒
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
// 关闭Cursor释放资源
c.close();
}
}
}

@ -1,19 +1,9 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* AlarmReceiver - 广
* 广Activity
*
* 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.
* extends BroadcastReceiver: AndroidBroadcastReceiver
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
@ -21,10 +11,20 @@ import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
/*
* onReceive - 广
* 广AlarmAlertActivity
*
* @param context
* @param intent 广
*/
@Override
public void onReceive(Context context, Intent intent) {
// 设置Intent的类以便启动AlarmAlertActivity
intent.setClass(context, AlarmAlertActivity.class);
// 添加标志表示在一个新的任务中启动Activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 根据设置的Intent启动Activity
context.startActivity(intent);
}
}

@ -11,8 +11,7 @@ import android.widget.EditText;
import android.widget.Toast;
import net.micode.notes.R;
public class ChangingPassword extends Activity {
public class ChangePassword extends Activity {
EditText OldPassword;
EditText NewPassword;
EditText AckPassword;
@ -26,9 +25,9 @@ public class ChangingPassword extends Activity {
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
OldPassword=(EditText) findViewById(R.id.old_password);
NewPassword=(EditText) findViewById(R.id.new_password);
AckPassword=(EditText) findViewById(R.id.ack_password);
Acknowledged=(Button)findViewById(R.id.Acknowledged);
NewPassword=(EditText) findViewById(R.id.new_password);// 获取确认密码的EditText
AckPassword=(EditText) findViewById(R.id.ack_password);// 获取确认按钮
Acknowledged=(Button)findViewById(R.id.Acknowledged);// 为确认按钮设置点击事件监听器
Acknowledged.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -38,20 +37,20 @@ public class ChangingPassword extends Activity {
SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE);
String login_password=pref.getString("password","");
if(old_password.equals("")==true || new_password.equals("")==true || ack_password.equals("")==true) {
Toast.makeText(ChangingPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show();
Toast.makeText(ChangePassword.this, "密码不能为空", Toast.LENGTH_SHORT).show();
}else if (new_password.equals(ack_password) == false) {
Toast.makeText(ChangingPassword.this, "新建密码与重复密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show();
Toast.makeText(ChangePassword.this, "新建密码与重复密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show();
AckPassword.setText("");
}else if(old_password.equals(login_password) == false){
Toast.makeText(ChangingPassword.this, "原有密码错误,请重新输入密码", Toast.LENGTH_SHORT).show();
Toast.makeText(ChangePassword.this, "原有密码错误,请重新输入密码", Toast.LENGTH_SHORT).show();
OldPassword.setText("");
}
else if (new_password.equals(ack_password) == true && old_password.equals(login_password) == true){
SharedPreferences.Editor editor=getSharedPreferences("user management", MODE_PRIVATE).edit();
editor.putString("password",new_password);
editor.apply();
Toast.makeText(ChangingPassword.this, "修改密码成功", Toast.LENGTH_SHORT).show();
Intent intent=new Intent(ChangingPassword.this,NotesListActivity.class);
Toast.makeText(ChangePassword.this, "修改密码成功", Toast.LENGTH_SHORT).show();
Intent intent=new Intent(ChangePassword.this,NotesListActivity.class);
startActivity(intent);
finish();
}
@ -61,7 +60,7 @@ public class ChangingPassword extends Activity {
@Override
public void onBackPressed() {
Intent intent=new Intent(ChangingPassword.this,NotesListActivity.class);
Intent intent=new Intent(ChangePassword.this,NotesListActivity.class);
startActivity(intent);
finish();
}

@ -30,55 +30,85 @@ import android.widget.NumberPicker;
public class DateTimePicker extends FrameLayout {
// 默认启用状态
private static final boolean DEFAULT_ENABLE_STATE = true;
// 半天的小时数
private static final int HOURS_IN_HALF_DAY = 12;
// 一整天的小时数
private static final int HOURS_IN_ALL_DAY = 24;
// 一周的天数
private static final int DAYS_IN_ALL_WEEK = 7;
// 日期选择器的最小值
private static final int DATE_SPINNER_MIN_VAL = 0;
// 日期选择器的最大值
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
// 24小时制小时选择器的最小值
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
// 24小时制小时选择器的最大值
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
// 12小时制小时选择器的最小值
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
// 12小时制小时选择器的最大值
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
// 分钟选择器的最小值
private static final int MINUT_SPINNER_MIN_VAL = 0;
// 分钟选择器的最大值
private static final int MINUT_SPINNER_MAX_VAL = 59;
// 上下午选择器的最小值
private static final int AMPM_SPINNER_MIN_VAL = 0;
// 上下午选择器的最大值
private static final int AMPM_SPINNER_MAX_VAL = 1;
// 日期选择器
private final NumberPicker mDateSpinner;
// 小时选择器
private final NumberPicker mHourSpinner;
// 分钟选择器
private final NumberPicker mMinuteSpinner;
// 上下午选择器
private final NumberPicker mAmPmSpinner;
// 当前日期
private Calendar mDate;
// 用于显示日期的字符串数组
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 当前是否为上午
private boolean mIsAm;
// 当前是否为24小时制视图
private boolean mIs24HourView;
// 控件是否启用
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
// 是否正在初始化
private boolean mInitialising;
// 日期时间改变监听器
private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 日期选择器的值改变监听器
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 根据新旧值的差异更新日期
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
}
};
// 小时选择器的值改变监听器
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 根据小时的变化更新日期和上下午状态
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
// 处理12小时制下的日期变化和上下午切换
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
@ -94,6 +124,7 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl();
}
} else {
// 处理24小时制下的日期变化
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
@ -104,9 +135,11 @@ public class DateTimePicker extends FrameLayout {
isDateChanged = true;
}
}
// 更新小时并触发日期时间改变事件
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
// 如果日期有变化,则更新年月日
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
@ -115,9 +148,12 @@ public class DateTimePicker extends FrameLayout {
}
};
// 分别为分钟和AM/PM选择器监听器设置匿名内部类实现数值变化时的处理逻辑。
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 计算小时的偏移量,当从最大值变为最小值或从最小值变为最大值时调整
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
@ -126,6 +162,7 @@ public class DateTimePicker extends FrameLayout {
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
// 根据偏移量更新日期和小时选择器并检查是否需要切换AM/PM
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
@ -139,6 +176,7 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl();
}
}
// 更新分钟值并触发日期变化的回调
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
@ -147,6 +185,7 @@ public class DateTimePicker extends FrameLayout {
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 切换AM/PM状态并更新日期和AM/PM选择器
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
@ -158,19 +197,23 @@ public class DateTimePicker extends FrameLayout {
}
};
// 定义日期时间变化的回调接口
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
// 构造函数:初始化日期时间选择器
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
// 构造函数:指定初始日期时间
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
// 构造函数指定是否使用24小时制视图
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();
@ -178,6 +221,7 @@ public class DateTimePicker extends FrameLayout {
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);
// 初始化日期、小时、分钟和AM/PM选择器并设置相应的监听器
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
@ -198,28 +242,37 @@ public class DateTimePicker extends FrameLayout {
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
// 更新控件至初始状态
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
// 设置为当前时间
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
// 设置内容描述
mInitialising = false;
}
/**
*
*
*
*
* @param enabled
*/
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
// 同时启用或禁用日期和时间选择器
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
@ -227,43 +280,52 @@ public class DateTimePicker extends FrameLayout {
mIsEnabled = enabled;
}
/**
*
*
* @return
*/
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
*
* @return the current date in millis
* @return
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
*
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
// 通过日历实例的详细字段设置当前日期和时间
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
*
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
* @param year
* @param month
* @param dayOfMonth
* @param hourOfDay
* @param minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 分别设置年、月、日、时和分
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
@ -271,88 +333,101 @@ public class DateTimePicker extends FrameLayout {
setCurrentMinute(minute);
}
/**
* Get current year
*
*
* @return The current year
* @return
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
*
* @param year The current year
* @param year
*/
public void setCurrentYear(int year) {
// 如果不是初始化状态并且设置的年份与当前年份相同,则直接返回
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
updateDateControl(); // 更新日期控件
onDateTimeChanged(); // 触发日期时间改变事件
}
/**
* Get current month in the year
*
*
* @return The current month in the year
* @return 0
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
*
* @param month The month in the year
* @param month 0
*/
public void setCurrentMonth(int month) {
// 如果不是初始化状态并且设置的月份与当前月份相同,则直接返回
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
updateDateControl(); // 更新日期控件
onDateTimeChanged(); // 触发日期时间改变事件
}
/**
* Get current day of the month
*
*
* @return The day of the month
* @return
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
*
* @param dayOfMonth The day of the month
* @param dayOfMonth
*/
public void setCurrentDay(int dayOfMonth) {
// 如果不是初始化状态并且设置的日期与当前日期相同,则直接返回
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
updateDateControl(); // 更新日期控件
onDateTimeChanged(); // 触发日期时间改变事件
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
* 24(0~23)
*
* @return 24
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
* 24
* 24{@link #getCurrentHourOfDay()}
* 12/
*
* @return 12
*/
private int getCurrentHour() {
if (mIs24HourView){
if (mIs24HourView) {
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
// 转换为12小时制
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
@ -361,16 +436,19 @@ public class DateTimePicker extends FrameLayout {
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
* 24(0~23)
*
* @param hourOfDay
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
// 如果在初始化中或者小时未改变,则直接返回
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 如果不是24小时视图则调整小时数并更新AM/PM控制
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
@ -390,18 +468,21 @@ public class DateTimePicker extends FrameLayout {
}
/**
* Get currentMinute
*
*
* @return The Current Minute
* @return
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*
*
* @param minute
*/
public void setCurrentMinute(int minute) {
// 如果在初始化中或者分钟数未改变,则直接返回
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
@ -411,18 +492,21 @@ public class DateTimePicker extends FrameLayout {
}
/**
* @return true if this is in 24 hour view else false.
* 24
*
* @return 24truefalse
*/
public boolean is24HourView () {
public boolean is24HourView() {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
* 24AM/PM
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
* @param is24HourView true24falseAM/PM
*/
public void set24HourView(boolean is24HourView) {
// 如果视图模式未改变,则直接返回
if (mIs24HourView == is24HourView) {
return;
}
@ -434,11 +518,15 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl();
}
/**
*
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
// 循环设置一周内每一天的显示文本
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
@ -448,6 +536,9 @@ public class DateTimePicker extends FrameLayout {
mDateSpinner.invalidate();
}
/**
* 24AM/PM
*/
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
@ -458,6 +549,9 @@ public class DateTimePicker extends FrameLayout {
}
}
/**
* 24
*/
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
@ -469,13 +563,17 @@ public class DateTimePicker extends FrameLayout {
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*
*
* @param callback null
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
/**
*
*/
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),

@ -1,19 +1,7 @@
/*
* 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.
* DateTimePickerDialog
*
*/
package net.micode.notes.ui;
import java.util.Calendar;
@ -31,48 +19,84 @@ import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
// 当前选择的日期和时间
private Calendar mDate = Calendar.getInstance();
// 用于指示日期时间选择器是否使用24小时制
private boolean mIs24HourView;
// 日期时间设置监听器,用于处理日期时间选择后的回调
private OnDateTimeSetListener mOnDateTimeSetListener;
// 日期时间选择器视图
private DateTimePicker mDateTimePicker;
/**
*
* OnDateTimeSet
*/
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
/**
*
*
* @param context Activity
* @param date
*/
public DateTimePickerDialog(Context context, long date) {
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
// 设置日期时间改变的监听器
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 更新内部日期时间值
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
// 更新对话框的标题显示
updateTitle(mDate.getTimeInMillis());
}
});
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框的确认和取消按钮
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener) null);
// 根据系统设置决定是否使用24小时制
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 更新标题以显示当前选择的日期和时间
updateTitle(mDate.getTimeInMillis());
}
/**
* 使24
*
* @param is24HourView 使24
*/
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
/**
*
*
* @param callBack
*/
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
/**
*
*
* @param date
*/
private void updateTitle(long date) {
// 根据是否使用24小时制来格式化日期时间显示
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
@ -81,6 +105,13 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
/**
*
* OnDateTimeSet
*
* @param arg0
* @param arg1
*/
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());

@ -12,7 +12,7 @@ import android.widget.Toast;
import net.micode.notes.R;
public class DeletingPassword extends Activity {
public class DeletePassword extends Activity {
EditText Dt_password;
Button Acknowledged;
@ -30,7 +30,7 @@ public class DeletingPassword extends Activity {
public void onClick(View v) {
String text02 = Dt_password.getText().toString();
if(text02.equals("")==true)
Toast.makeText(DeletingPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show();
Toast.makeText(DeletePassword.this, "密码不能为空", Toast.LENGTH_SHORT).show();
SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE);
String password = pref.getString("password","");
if(password.equals("")==false&&password.equals(text02)==true){
@ -40,13 +40,13 @@ public class DeletingPassword extends Activity {
editor.putString("password","");
editor.apply();
Toast.makeText(DeletingPassword.this, "已经删除登录密码", Toast.LENGTH_SHORT).show();
Intent intent=new Intent(DeletingPassword.this,NotesListActivity.class);
Toast.makeText(DeletePassword.this, "已经删除登录密码", Toast.LENGTH_SHORT).show();
Intent intent=new Intent(DeletePassword.this,NotesListActivity.class);
startActivity(intent);
finish();
}
else{
Toast.makeText(DeletingPassword.this, "密码错误", Toast.LENGTH_SHORT).show();
Toast.makeText(DeletePassword.this, "密码错误", Toast.LENGTH_SHORT).show();
Dt_password.setText("");//把密码框内输入过的错误密码清空
}
}
@ -55,7 +55,7 @@ public class DeletingPassword extends Activity {
@Override
public void onBackPressed() {
Intent intent=new Intent(DeletingPassword.this,NotesListActivity.class);
Intent intent=new Intent(DeletePassword.this,NotesListActivity.class);
startActivity(intent);
finish();
}

@ -1,19 +1,7 @@
/*
* 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.
* DropdownMenu
* ButtonPopupMenuButton
*/
package net.micode.notes.ui;
import android.content.Context;
@ -28,16 +16,24 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
private Menu mMenu;
private Button mButton; // 弹出下拉菜单的按钮
private PopupMenu mPopupMenu; // 弹出的下拉菜单
private Menu mMenu; // 下拉菜单的项目集合
/**
* DropdownMenu
*
* @param context Activity
* @param button
* @param menuId ID
*/
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景为下拉图标
mPopupMenu = new PopupMenu(context, mButton); // 创建PopupMenu实例
mMenu = mPopupMenu.getMenu(); // 获取菜单项的集合
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 加载菜单项
// 设置按钮点击事件,点击后显示下拉菜单
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
@ -45,16 +41,32 @@ public class DropdownMenu {
});
}
/**
*
*
* @param listener PopupMenuOnMenuItemClickListener
*/
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
/**
* ID
*
* @param id ID
* @return MenuItemnull
*/
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
/**
*
*
* @param title
*/
public void setTitle(CharSequence title) {
mButton.setText(title);
}

@ -1,21 +1,12 @@
/*
* 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.
* FoldersListAdapter
* CursorAdapter
*/
package net.micode.notes.ui;
// 导入相关类
import android.content.Context;
import android.database.Cursor;
import android.view.View;
@ -30,48 +21,87 @@ import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
public static final String [] PROJECTION = {
// 查询时使用的列名数组
public static final String[] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
// 列名数组中的索引常量
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
/*
*
* @param context ActivityApplication
* @param c Cursor
*/
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
/*
* View
* @param context
* @param cursor
* @param parent
* @return View
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
/*
* View
* @param view
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
// 根据文件夹ID判断是根文件夹还是普通文件夹并设置文件夹名称
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
/*
*
* @param context
* @param position
* @return
*/
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
/*
* FolderListItem
*
*/
private class FolderListItem extends LinearLayout {
private TextView mName;
private TextView mName; // 文件夹名称的文本视图
/*
*
* @param context
*/
public FolderListItem(Context context) {
super(context);
// 加载布局文件,并将自己作为根视图
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
mName = (TextView) findViewById(R.id.tv_folder_name); // 获取文件夹名称的视图
}
/*
*
* @param name
*/
public void bind(String name) {
mName.setText(name);
}

@ -1,43 +0,0 @@
package net.micode.notes.ui;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import net.micode.notes.R;
public class GetLocation extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.local);
Toast.makeText(GetLocation.this,"init",Toast.LENGTH_SHORT).show();
Button local_1 = new Button(this);
Button local_2 = new Button(this);
local_1.setOnClickListener(new View.OnClickListener() {
private static final String TAG = "GetLocation";
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: button1");
Toast.makeText(GetLocation.this,"button1",Toast.LENGTH_SHORT).show();
}
});
local_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(GetLocation.this, "button2", Toast.LENGTH_SHORT).show();
}
});
}
}

@ -24,7 +24,7 @@ public class LoginActivity extends Activity {
boolean User_boolean = pref.getBoolean("user",false);//获取用户是否设置了密码
if(!User_boolean) //User_boolean = false时没有设置密码直接跳转到便签主界面
{
Intent intent=new Intent(LoginActivity.this,NotesListActivity.class);
Intent intent=new Intent(LoginActivity.this,SplashActivity.class);//跳转到欢迎界面splash内含跳转
startActivity(intent);
finish();
}
@ -41,7 +41,7 @@ public class LoginActivity extends Activity {
SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE);
String password=pref.getString("password","");
if(password.equals("")==false&&password.equals(lg_password.getText().toString())==true){
Intent intent=new Intent(LoginActivity.this,NotesListActivity.class);
Intent intent=new Intent(LoginActivity.this,SplashActivity.class);
startActivity(intent);
finish();
}

@ -37,16 +37,21 @@ import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
/**
*
*/
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private int mIndex; // 当前文本视图的索引
private int mSelectionStartBeforeDelete; // 删除操作前的选择起始位置
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
private static final String SCHEME_TEL = "tel:";
private static final String SCHEME_HTTP = "http:";
private static final String SCHEME_EMAIL = "mailto:";
// URL方案与对应操作资源ID的映射
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
@ -54,56 +59,84 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
/**
*
*
* @param context
*/
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
/**
*
*
* @param index
*/
public void setIndex(int index) {
mIndex = index;
}
/**
*
*
* @param listener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
/**
*
*
* @param context
* @param attrs
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
/**
*
*
* @param context
* @param attrs
* @param defStyle
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
/**
*
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算触摸位置相对于文本的偏移量,并调整光标位置
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
@ -121,15 +154,20 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
return super.onTouchEvent(event);
}
/**
*
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 处理回车键事件,准备新增文本视图
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除操作前的选择位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
@ -138,10 +176,14 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
return super.onKeyDown(keyCode, event);
}
/**
*
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_DEL:
// 处理删除键事件,若为首个文本且非空,则删除当前文本
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
@ -152,6 +194,7 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
}
break;
case KeyEvent.KEYCODE_ENTER:
// 处理回车键事件,新增文本视图
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
@ -167,6 +210,9 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
return super.onKeyUp(keyCode, event);
}
/**
*
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
@ -179,6 +225,9 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
* URL
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
@ -191,8 +240,8 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
for (String schema : sSchemaActionResMap.keySet()) {
if (urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
@ -205,7 +254,7 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
// 跳转到URL指向的页面
urls[0].onClick(NoteEditText.this);
return true;
}
@ -215,3 +264,4 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText {
super.onCreateContextMenu(menu);
}
}

@ -26,8 +26,12 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
/**
*
*/
public class NoteItemData {
static final String [] PROJECTION = new String [] {
// 定义查询时要投影的列
static final String[] PROJECTION = new String[]{
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
@ -40,9 +44,9 @@ public class NoteItemData {
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.TOP,
};
// 各列数据的索引
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
@ -55,8 +59,8 @@ 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 TOP_ID_COLUMN = 12;
// 笔记的各项数据
private long mId;
private long mAlertDate;
private int mBgColorId;
@ -71,15 +75,22 @@ public class NoteItemData {
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private String mTop;
// 用于标识笔记在列表中的位置状态
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
/**
* CursorNoteItemData
*
* @param context 访
* @param cursor Cursor
*/
public NoteItemData(Context context, Cursor cursor) {
// 从Cursor中提取各项数据并赋值
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
@ -94,8 +105,8 @@ public class NoteItemData {
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mTop = cursor.getString(TOP_ID_COLUMN);
// 如果是通话记录笔记,尝试获取通话号码和联系人名称
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
@ -107,19 +118,27 @@ public class NoteItemData {
}
}
// 如果没有获取到联系人名称,则默认为空字符串
if (mName == null) {
mName = "";
}
checkPostion(cursor);
}
/**
* CursorNoteItemData
*
* @param cursor Cursor
*/
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
// 更新位置状态信息
mIsLastItem = cursor.isLast();
mIsFirstItem = cursor.isFirst();
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
// 检查当前笔记是否跟随文件夹,并更新相应状态
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
@ -131,6 +150,7 @@ public class NoteItemData {
mIsOneNoteFollowingFolder = true;
}
}
// 确保Cursor能够回到原来的位置
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
@ -138,13 +158,7 @@ public class NoteItemData {
}
}
public boolean isTOP() {
if(mTop.equals("1")) {
return true;
}else {
return false;
}
}
// 以下为获取NoteItemData各项属性的方法
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
@ -202,7 +216,7 @@ public class NoteItemData {
return mNotesCount;
}
public long getFolderId () {
public long getFolderId() {
return mParentId;
}
@ -230,7 +244,14 @@ public class NoteItemData {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
/**
* Cursor
*
* @param cursor Cursor
* @return
*/
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -31,18 +31,32 @@ import java.util.HashSet;
import java.util.Iterator;
/**
* CursorAdapter
*/
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter";
private Context mContext;
// 用于存储选中项的索引和状态
private HashMap<Integer, Boolean> mSelectedIndex;
private int mNotesCount;
private boolean mChoiceMode;
private int mNotesCount; // 笔记总数
private boolean mChoiceMode; // 选择模式标志
/**
* AppWidget
*/
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
};
public int widgetId; // 小部件ID
public int widgetType; // 小部件类型
}
;
/**
*
*
* @param context
*/
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
@ -50,11 +64,26 @@ public class NotesListAdapter extends CursorAdapter {
mNotesCount = 0;
}
/**
*
*
* @param context
* @param cursor
* @param parent
* @return
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
/**
*
*
* @param view
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
@ -64,20 +93,41 @@ public class NotesListAdapter extends CursorAdapter {
}
}
/**
*
*
* @param position
* @param checked
*/
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
/**
*
*
* @return
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/**
*
*
* @param mode
*/
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
/**
*
*
* @param checked
*/
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
@ -89,6 +139,11 @@ public class NotesListAdapter extends CursorAdapter {
}
}
/**
* ID
*
* @return IDHashSet
*/
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
@ -105,6 +160,11 @@ public class NotesListAdapter extends CursorAdapter {
return itemSet;
}
/**
*
*
* @return HashSet
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
@ -116,9 +176,6 @@ public class NotesListAdapter extends CursorAdapter {
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
@ -128,6 +185,11 @@ public class NotesListAdapter extends CursorAdapter {
return itemSet;
}
/**
*
*
* @return
*/
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
@ -143,11 +205,22 @@ public class NotesListAdapter extends CursorAdapter {
return count;
}
/**
*
*
* @return
*/
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
/**
*
*
* @param position
* @return
*/
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
@ -155,18 +228,29 @@ public class NotesListAdapter extends CursorAdapter {
return mSelectedIndex.get(position);
}
/**
*
*/
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
/**
*
*
* @param cursor
*/
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
/**
*
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
@ -182,3 +266,4 @@ public class NotesListAdapter extends CursorAdapter {
}
}
}

@ -30,27 +30,43 @@ import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/*
* LinearLayout
* UI
*/
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData;
private CheckBox mCheckBox;
private ImageView mTop;
private ImageView mAlert; // 用于显示提醒图标
private TextView mTitle; // 显示笔记标题
private TextView mTime; // 显示修改时间
private TextView mCallName; // 在通话记录笔记中显示通话名称
private NoteItemData mItemData; // 绑定的笔记数据
private CheckBox mCheckBox; // 选择框,用于多选模式
/*
*
*/
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
// 初始化视图组件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
mTop = (ImageView) findViewById(R.id.iv_top_icon);
}
/*
*
*
* @param context
* @param data
* @param choiceMode
* @param checked
*/
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 根据是否为选择模式和笔记类型,控制复选框的可见性和选中状态
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
@ -59,7 +75,9 @@ public class NotesListItem extends LinearLayout {
}
mItemData = data;
// 根据笔记类型和状态,设置标题、提醒图标和背景
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 通话记录文件夹
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@ -67,9 +85,10 @@ public class NotesListItem extends LinearLayout {
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 通话记录笔记
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
@ -78,6 +97,7 @@ public class NotesListItem extends LinearLayout {
mAlert.setVisibility(View.GONE);
}
} else {
// 其他类型的笔记或文件夹
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@ -95,23 +115,21 @@ public class NotesListItem extends LinearLayout {
mAlert.setVisibility(View.GONE);
}
}
if(data.isTOP()){
mTop.setImageResource(R.drawable.menu_top);
mTop.setVisibility(View.VISIBLE);
} else{
mTop.setVisibility((View.GONE));
}
}
// 设置时间显示
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置背景资源
setBackground(data);
}
/*
*
*/
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
// 根据笔记的状态设置不同的背景资源
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
@ -122,11 +140,18 @@ public class NotesListItem extends LinearLayout {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
// 文件夹背景资源
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
/*
*
*
* @return NoteItemData
*/
public NoteItemData getItemData() {
return mItemData;
}
}

@ -49,51 +49,56 @@ import net.micode.notes.gtask.remote.GTaskSyncService;
public class NotesPreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "notes_preferences";
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory;
private GTaskReceiver mReceiver;
private Account[] mOriAccounts;
private boolean mHasAddedAccount;
// 常量定义部分:主要用于设置和同步相关的偏好设置键
public static final String PREFERENCE_NAME = "notes_preferences"; // 偏好设置的名称
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; // 同步账户名称的键
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; // 上次同步时间的键
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; // 设置背景颜色的键
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; // 同步账户的键
private static final String AUTHORITIES_FILTER_KEY = "authorities"; // 权限过滤键
// 类成员变量定义部分主要用于账户同步和UI更新
private PreferenceCategory mAccountCategory; // 账户分类偏好项
private GTaskReceiver mReceiver; // 接收同步任务的广播接收器
private Account[] mOriAccounts; // 原始账户数组
private boolean mHasAddedAccount; // 标记是否已添加新账户
/**
* Activity
*
*
* @param icicle ActivityBundle
*/
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
/* using the app icon for navigation */
// 设置返回按钮
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从XML加载偏好设置
addPreferencesFromResource(R.xml.preferences);
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
registerReceiver(mReceiver, filter); // 注册广播接收器以监听同步服务
mOriAccounts = null;
// 添加设置头部视图
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
/**
* Activity
*
*/
@Override
protected void onResume() {
super.onResume();
// need to set sync account automatically if user has added a new
// account
// 自动设置新添加的账户进行同步
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
@ -106,43 +111,53 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
if (!found) {
setSyncAccount(accountNew.name);
setSyncAccount(accountNew.name); // 设置新账户进行同步
break;
}
}
}
}
// 刷新UI
refreshUI();
}
/**
* Activity广
*/
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
unregisterReceiver(mReceiver); // 注销广播接收器,避免内存泄漏
}
super.onDestroy();
}
/**
*
*/
private void loadAccountPreference() {
mAccountCategory.removeAll();
mAccountCategory.removeAll(); // 清空账户分类下的所有条目
// 创建并配置账户偏好项
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
final String defaultAccount = getSyncAccountName(this); // 获取默认同步账户名称
accountPref.setTitle(getString(R.string.preferences_account_title)); // 设置标题
accountPref.setSummary(getString(R.string.preferences_account_summary)); // 设置摘要
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// 处理账户点击事件
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account
// 如果尚未设置账户,则展示选择账户对话框
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
// 如果已经设置账户,则展示更改账户确认对话框
showChangeAccountConfirmAlertDialog();
}
} else {
// 如果正在同步中,则展示无法更改账户的提示
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
@ -151,54 +166,66 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
});
mAccountCategory.addPreference(accountPref);
mAccountCategory.addPreference(accountPref); // 将账户偏好项添加到账户分类下
}
/**
*
*/
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
Button syncButton = (Button) findViewById(R.id.preference_sync_button); // 获取同步按钮
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); // 获取上次同步时间视图
// set button state
// 根据同步状态设置按钮文本和点击事件
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setText(getString(R.string.preferences_button_sync_cancel)); // 设置为取消同步文本
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
GTaskSyncService.cancelSync(NotesPreferenceActivity.this); // 设置点击事件为取消同步
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setText(getString(R.string.preferences_button_sync_immediately)); // 设置为立即同步文本
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
GTaskSyncService.startSync(NotesPreferenceActivity.this); // 设置点击事件为开始同步
}
});
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); // 只有在设置了同步账户时才使能同步按钮
// set last sync time
// 根据同步状态设置上次同步时间的显示
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
lastSyncTimeView.setText(GTaskSyncService.getProgressString()); // 如果正在同步,显示进度信息
lastSyncTimeView.setVisibility(View.VISIBLE); // 显示上次同步时间视图
} else {
long lastSyncTime = getLastSyncTime(this);
long lastSyncTime = getLastSyncTime(this); // 获取上次同步时间
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
lastSyncTime))); // 格式化并显示上次同步时间
lastSyncTimeView.setVisibility(View.VISIBLE); // 显示上次同步时间视图
} else {
lastSyncTimeView.setVisibility(View.GONE);
lastSyncTimeView.setVisibility(View.GONE); // 如果未同步过,则隐藏上次同步时间视图
}
}
}
/**
*
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
loadAccountPreference(); // 加载账户偏好设置
loadSyncButton(); // 加载同步按钮
}
/**
*
* Google
*
*/
private void showSelectAccountAlertDialog() {
// 创建对话框构建器并设置自定义标题
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
@ -208,18 +235,20 @@ public class NotesPreferenceActivity extends PreferenceActivity {
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
dialogBuilder.setPositiveButton(null, null); // 移除默认的确定按钮
// 获取当前设备上的Google账户
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
String defAccount = getSyncAccountName(this); // 获取当前同步的账户名称
mOriAccounts = accounts;
mHasAddedAccount = false;
mOriAccounts = accounts; // 保存原始账户列表
mHasAddedAccount = false; // 标记是否已添加新账户
if (accounts.length > 0) {
// 创建账户选项并设置选中项
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int checkedItem = -1; // 记录默认选中的账户
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
@ -227,6 +256,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
items[index++] = account.name;
}
// 设置单选列表,并为选中的账户执行同步操作
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@ -237,15 +267,17 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
// 添加“添加账户”选项
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
final AlertDialog dialog = dialogBuilder.show();
// 点击“添加账户”执行添加账户操作
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[]{
"gmail-ls"
});
startActivityForResult(intent, -1);
@ -254,9 +286,14 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
/**
*
*
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 设置自定义标题,包含当前同步账户名称
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
@ -265,7 +302,8 @@ public class NotesPreferenceActivity extends PreferenceActivity {
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
dialogBuilder.setCustomTitle(titleView);
CharSequence[] menuItemArray = new CharSequence[] {
// 创建菜单项并设置点击事件
CharSequence[] menuItemArray = new CharSequence[]{
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
@ -273,8 +311,10 @@ public class NotesPreferenceActivity extends PreferenceActivity {
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 选择更改账户,显示账户选择对话框
showSelectAccountAlertDialog();
} else if (which == 1) {
// 选择移除账户执行移除操作并刷新UI
removeSyncAccount();
refreshUI();
}
@ -283,15 +323,29 @@ public class NotesPreferenceActivity extends PreferenceActivity {
dialogBuilder.show();
}
/**
* Google
*
* @return Account[] com.google
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/**
*
* SharedPreferencesgtask
*
* @param account
*/
private void setSyncAccount(String account) {
// 检查当前账户是否与传入账户名一致,不一致则更新账户信息
if (!getSyncAccountName(this).equals(account)) {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 如果账户名非空,则保存账户名,否则清除账户名
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
@ -299,10 +353,10 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
editor.commit();
// clean up last sync time
// 清理上次同步时间
setLastSyncTime(this, 0);
// clean up local gtask related info
// 清理本地相关的gtask信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -312,24 +366,31 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}).start();
// 显示设置成功的提示信息
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
/**
*
* SharedPreferencesgtask
*/
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 如果存在账户信息,则移除
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
// 如果存在上次同步时间信息,则移除
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
editor.commit();
// clean up local gtask related info
// 清理本地相关的gtask信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -340,12 +401,26 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}).start();
}
/**
*
* SharedPreferences
*
* @param context
* @return
*/
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/**
*
* SharedPreferences
*
* @param context
* @param time
*/
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
@ -354,17 +429,28 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit();
}
/**
*
* SharedPreferences0
*
* @param context
* @return
*/
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
/**
* 广gtask广UI
*/
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
refreshUI();
// 如果广播消息表明正在同步则更新UI显示的同步状态信息
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
@ -374,9 +460,16 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
*
*
* @param item
* @return truefalse
*/
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// 当选择返回按钮时启动NotesListActivity并清除当前活动栈顶以上的所有活动
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

@ -15,7 +15,7 @@ import android.widget.Toast;
import net.micode.notes.R;
public class SettingPassword extends Activity {
public class SetPassword extends Activity {
EditText password;
EditText password_ack;
Button acknowledge;
@ -36,9 +36,9 @@ public class SettingPassword extends Activity {
String text02 = password.getText().toString();
String text03 = password_ack.getText().toString();
if(text02.equals("")==true) {
Toast.makeText(SettingPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show();
Toast.makeText(SetPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show();
}else if (text02.equals(text03) == false) {
Toast.makeText(SettingPassword.this, "密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show();
Toast.makeText(SetPassword.this, "密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show();
password_ack.setText("");
}else if (text02.equals(text03) == true){
SharedPreferences.Editor editor=getSharedPreferences("user management",
@ -47,8 +47,8 @@ public class SettingPassword extends Activity {
editor.putString("password",text02);
editor.apply();
Log.d("RegisterLoginPassword","password is "+text02);
Toast.makeText(SettingPassword.this, "设置密码成功", Toast.LENGTH_SHORT).show();
Intent intent=new Intent(SettingPassword.this,NotesListActivity.class);
Toast.makeText(SetPassword.this, "设置密码成功", Toast.LENGTH_SHORT).show();
Intent intent=new Intent(SetPassword.this,NotesListActivity.class);
startActivity(intent);
finish();
}
@ -59,7 +59,7 @@ public class SettingPassword extends Activity {
@Override
public void onBackPressed() {
Intent intent=new Intent(SettingPassword.this,NotesListActivity.class);
Intent intent=new Intent(SetPassword.this,NotesListActivity.class);
startActivity(intent);
finish();
}

@ -1,31 +1,33 @@
package net.micode.notes.ui;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;
import net.micode.notes.R;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*/
public class SplashActivity extends AppCompatActivity {
Handler mHandler=new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //加载启动界面
setContentView(R.layout.activity_splash); //加载启动
// 当计时结束时跳转至NotesListActivity
mHandler.postDelayed(new Runnable() {
setContentView(R.layout.activity_splash); //加载启动图片
// 当计时结束时跳转至NotesListActivity
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Intent intent=new Intent();
intent.setClass(SplashActivity.this, LoginActivity.class);
intent.setClass(SplashActivity.this, NotesListActivity.class);
startActivity(intent);
finish(); //销毁欢迎页面
}}, 2000); // 2 秒后跳转}
}
}, 3000); // 2 秒后跳转
}
}

@ -4,8 +4,19 @@ import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
//源URL https://fanyi-api.baidu.com/api/trans/vip/translate
//参数如下
// String q 英文单词/中文
// String from 原始语种 zh中文/eh英文
// String to 目标语种 zh中文/eh英文
// String from zh中文/eh英文
// String appid 你的appid
// String salt 随机数(整形转字符串)
// String sign 签名 32位字母小写MD5编码的 appid+q+salt+密钥
public interface BaiduTranslateService {
//翻译接口
//表示提交表单数据,@Field注解键名
//适用于数据量少的情况
@POST("translate")
@FormUrlEncoded
Call<RespondBean> translate(@Field("q") String q, @Field("from") String from, @Field("to") String to, @Field("appid") String appid, @Field("salt") String salt,

@ -2,7 +2,7 @@ package net.micode.notes.ui.translate_demo;
import java.util.List;
public class RespondBean {
public class RespondBean {
/**
* from : zh
@ -43,7 +43,6 @@ import java.util.List;
* src :
* dst : Hello
*/
private String src;
private String dst;

@ -1,20 +1,10 @@
/*
* 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.
* MiCode www.micode.net
* Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 查看许可证内容。
*/
package net.micode.notes.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@ -32,19 +22,28 @@ import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
/**
* AppWidgetProvider
*/
public abstract class NoteWidgetProvider extends AppWidgetProvider {
public static final String [] PROJECTION = new String [] {
// 查询笔记时用到的列名数组
public static final String[] PROJECTION = new String[]{
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};
// 列的索引常量
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
// 日志标签
private static final String TAG = "NoteWidgetProvider";
/**
* IDID
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
ContentValues values = new ContentValues();
@ -53,22 +52,44 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
new String[]{String.valueOf(appWidgetIds[i])});
}
}
/**
* ID
*
* @param context
* @param widgetId ID
* @return CursorID
*/
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
new String[]{String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
}
/**
*
*
* @param context
* @param appWidgetManager AppWidget
* @param appWidgetIds ID
*/
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
/**
*
*
* @param context
* @param appWidgetManager AppWidget
* @param appWidgetIds ID
* @param privacyMode
*/
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
@ -103,9 +124,8 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* Generate the pending intent to start host for the widget
*/
// 为小部件的点击事件设置PendingIntent
PendingIntent pendingIntent = null;
if (privacyMode) {
rv.setTextViewText(R.id.widget_text,
@ -124,9 +144,26 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
}
}
/**
* ID
*
* @param bgId ID
* @return ID
*/
protected abstract int getBgResourceId(int bgId);
/**
* ID
*
* @return ID
*/
protected abstract int getLayoutId();
/**
*
*
* @return
*/
protected abstract int getWidgetType();
}

@ -1,17 +1,10 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* MiCodewww.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
* Apache 2.0
* 访http://www.apache.org/licenses/LICENSE-2.0.html
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.micode.notes.widget;
@ -23,25 +16,51 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* 2xNoteWidgetProvider2x
*/
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
/**
*
*
* @param context 访
* @param appWidgetManager AppWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* ID
*
* @return ID
*/
@Override
protected int getLayoutId() {
return R.layout.widget_2x;
}
/**
* IDID
*
* @param bgId ID
* @return ID
*/
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
/**
*
*
* @return
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;
}
}

@ -1,17 +1,8 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* MiCodewww.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.
* Apache 2.0
* 访http://www.apache.org/licenses/LICENSE-2.0
*/
package net.micode.notes.widget;
@ -23,24 +14,51 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* 4x便NoteWidgetProvider
*
*/
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
/**
*
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* ID
*
* @return ID
*/
protected int getLayoutId() {
return R.layout.widget_4x;
}
/**
* ID
*
* @param bgId ID
* @return ID
*/
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
/**
*
*
* @return
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_4X;
}
}

@ -1,22 +1,17 @@
<?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
<!--
该文件定义了一个颜色选择器,用于根据视图的状态(按下、选中或其他)来改变其颜色。
版权归2010-2011年 MiCode 开源社区所有www.micode.net
该文件受 Apache 2.0 许可证保护,详细信息请参见许可证条款:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义按下状态的颜色采用半透明的黑色50% -->
<item android:state_pressed="true" android:color="#88555555" />
<!-- 定义选中状态的颜色,采用浅灰色 -->
<item android:state_selected="true" android:color="#ff999999" />
<!-- 默认颜色,即未按下且未选中的状态 -->
<item android:color="#ff000000" />
</selector>

@ -1,22 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此文件为颜色选择器的定义,用于根据不同的状态选择不同的颜色。
版权归 MiCode 开源社区所有 (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
遵循 Apache 2.0 许可证详情请访问http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
本文件定义了一个颜色选择器,但只包含了一个状态下的颜色定义。
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义默认状态下的颜色为半透明的黑色 -->
<item android:color="#50000000" />
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

@ -1,23 +1,17 @@
<?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.
<!--
此文件定义了一个选择器,用于根据按钮的状态显示不同的图片。
它是资源文件的一部分供Android应用程序使用。
版权归MiCode开源社区所有受Apache 2.0许可证保护。
详细信息见底部的LICENSE文件。
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 当按钮被按下时显示的图片 -->
<item android:state_pressed="true"
android:drawable="@drawable/new_note_pressed" />
<!-- 默认状态下的图片 -->
<item
android:drawable="@drawable/new_note_normal" />
</selector>

@ -1,37 +1,29 @@
<?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.
<!--
此XML文件定义了一个LinearLayout用于显示账户对话框的标题和子标题。
它是MiCode开源社区的一部分受Apache License 2.0保护。
详细信息见底部注释。
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 标题文本视图,用于显示对话框的标题 -->
<TextView
android:id="@+id/account_dialog_title"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-2.7dip"
android:layout_marginBottom="-2.7dip"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center"
android:singleLine="true" />
android:layout_marginTop="-2.7dip"
android:layout_marginBottom="-2.7dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<!-- 子标题文本视图,用于显示对话框的子标题信息 -->
<TextView
android:id="@+id/account_dialog_subtitle"
android:layout_width="fill_parent"

@ -3,20 +3,22 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
android:background="@drawable/splash"
tools:context=".ui.SplashActivity">
<!-- The primary full-screen view. This can be replaced with whatever view
is needed to present your content, e.g. VideoView, SurfaceView,
TextureView, etc. -->
<!-- android:text="@string/dummy_content" 意思是定义在res/values中的strings.xml中的量-->
<TextView
android:id="@+id/fullscreen_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:keepScreenOn="true"
android:text="@string/dummy_content"
android:textColor="#33b5e5"
android:background="@drawable/splash"
android:textSize="50sp"
android:textStyle="bold" />
@ -29,34 +31,23 @@
<LinearLayout
android:id="@+id/fullscreen_content_controls"
style="?metaButtonBarStyle"
style="@style/Widget.Theme.Notes.ButtonBar.Fullscreen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:background="@color/black_overlay"
android:orientation="horizontal"
tools:ignore="UselessParent">
<Button
android:id="@+id/dummy_button"
style="?metaButtonBarButtonStyle"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dummy_button" />
android:text="欢迎使用小米便签1.1" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/splash"
android:gravity="top"
android:keepScreenOn="true"
android:text="@string/dummy_content"
android:textSize="50dp"
android:textStyle="bold"
/>
</FrameLayout>
</FrameLayout>

@ -1,28 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此文件定义了一个LinearLayout布局用于在Android应用中显示一个添加账户的选项。
布局采用垂直方向排列主要包含一个TextView用于显示文本信息。
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.
版权声明2010-2011年MiCode开源社区www.micode.net
本文件受Apache软件基金会许可协议2.0版http://www.apache.org/licenses/LICENSE-2.0)保护。
除非符合许可协议条款,否则不得使用此文件。
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dip"
android:gravity="center_vertical"
android:orientation="vertical">
<!--
定义一个TextView组件用于显示“添加账户”的文本信息。
该组件的宽度根据内容自动调整,高度随内容而定,并且居中显示。
-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"

@ -1,18 +1,10 @@
<?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.
<!--
此布局文件用于创建一个水平线性布局包含四个NumberPicker组件用于选择日期、小时、分钟和AM/PM标志。
它是MiCode开源社区的一部分受Apache 2.0许可证保护。
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
@ -24,8 +16,7 @@
android:layout_width="120dip"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
/>
android:focusableInTouchMode="true" />
<NumberPicker
android:id="@+id/hour"
@ -33,8 +24,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
android:focusableInTouchMode="true" />
<NumberPicker
android:id="@+id/minute"
@ -42,8 +32,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
android:focusableInTouchMode="true" />
<NumberPicker
android:id="@+id/amPm"
@ -51,6 +40,5 @@
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
android:focusableInTouchMode="true" />
</LinearLayout>

@ -1,22 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此文件定义了一个EditText视图用于在Android应用中接受用户输入文件夹名。
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
版权声明2010-2011年MiCode开源社区(www.micode.net)
http://www.apache.org/licenses/LICENSE-2.0
本文件受Apache许可证2.0版保护,使用本文件需遵守该许可证规定。
有关许可证的具体内容请访问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.
除非适用法律要求或以书面形式同意,根据该许可证分发的软件是按“原样”分发的,
无任何明示或暗示的保证或条件。请参阅许可证以了解特定的使用权限和限制。
-->
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/et_foler_name"
android:layout_width="fill_parent"
android:hint="@string/hint_foler_name"

@ -1,25 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此文件定义了一个LinearLayout布局用于显示文件夹名称。
布局宽度和高度都匹配父容器最小高度为50dip。
布局内部包含一个TextView用于显示文件夹名称。
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
Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
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.
版权所有2010-2011年MiCode开源社区www.micode.net
本布局文件受Apache License, Version 2.0许可保护。
你可以在遵守许可条件下自由使用、复制、修改本文件。
详细许可信息请访问http://www.apache.org/licenses/LICENSE-2.0
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="50dip" >
android:minHeight="50dip">
<!--
定义一个TextView用于显示文件夹名称。
布局宽度和高度都匹配父容器,文本居中显示。
使用了"TextAppearancePrimaryItem"样式定义文本外观。
-->
<TextView
android:id="@+id/tv_folder_name"
android:layout_width="match_parent"

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
</LinearLayout>

@ -15,7 +15,7 @@
limitations under the License.
-->
<FrameLayout xmlns:tools="http://schemas.android.com/tools"
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background"
@ -25,24 +25,29 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- 坐标 -->
<TextView
android:id="@+id/local_sel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<!-- 标题栏 -->
<LinearLayout
android:id="@+id/note_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!-- code here -->
<TextView
android:id="@+id/note_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:layout_marginRight="8dip"
/>
<Button
android:id="@+id/iatBtn"
android:id="@+id/spkBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#6AE48E8E"
@ -56,14 +61,13 @@
android:background="#61B8F1F8"
android:text="翻译" />
<ImageButton
android:id="@+id/add_img_btn"
android:layout_width="45dp"
android:layout_height="match_parent"
android:src="@android:drawable/ic_menu_gallery" />
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="0dip"
@ -89,14 +93,13 @@
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageView
android:id="@+id/btn_set_bg_color"
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="10dp"
android:background="@drawable/bg_btn_set_color" />
<!-- 置顶 -->
<TextView
android:id="@+id/tv_set_top"
android:layout_width="wrap_content"
@ -105,9 +108,9 @@
android:layout_marginLeft="2dip"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
</LinearLayout>
<!-- 编辑区域 -->
<LinearLayout
android:id="@+id/sv_note_edit"
android:layout_width="fill_parent"
@ -133,6 +136,7 @@
android:layout_height="fill_parent"
android:orientation="horizontal">
<!-- 这里报错表示没有可读的屏幕阅读文本-->
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="fill_parent"
@ -155,12 +159,7 @@
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/num_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<!-- 还没有绑定对应按钮变量和实现响应函数-->
<Button
android:id="@+id/location"
android:layout_width="wrap_content"
@ -175,12 +174,15 @@
</LinearLayout>
</LinearLayout>
<!-- 背景颜色设置按钮 -->
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dip"
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
<!-- 背景颜色选择器 -->
<LinearLayout
android:id="@+id/note_bg_color_selector"
android:layout_width="wrap_content"

@ -1,25 +1,16 @@
<?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.
<!--
此文件定义了一个LinearLayout布局其中包含了一个CheckBox和一个NoteEditText。
布局宽度填充父容器,高度根据内容自动调整。
CheckBox用于复选操作NoteEditText用于输入文本。
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!-- 定义一个CheckBox用于编辑项的选择 -->
<CheckBox
android:id="@+id/cb_edit_item"
android:layout_width="wrap_content"
@ -28,6 +19,7 @@
android:focusable="false"
android:layout_gravity="top|left" />
<!-- 定义一个自定义的NoteEditText用于输入文本支持多行显示 -->
<net.micode.notes.ui.NoteEditText
android:id="@+id/et_edit_text"
android:layout_width="fill_parent"

@ -1,38 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此XML文件定义了一个FrameLayout布局用于显示一个便签项。
布局中包含了一个标题、时间、复选框和一个提示图标。
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
Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
http://www.apache.org/licenses/LICENSE-2.0
许可证信息: Apache License, Version 2.0
详细信息见: 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.
除非法律要求或以书面形式同意, 依据该许可证分发的软件在"原样"基础上分发,
不提供任何明示或暗示的保证。详见许可证文件中特定的权限和限制条款。
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/note_item"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<!-- 主要内容区域,线性布局管理器,垂直方向排列 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical">
<!-- 内容区域,线性布局管理器,垂直方向排列 -->
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<!-- 标题,通常不可见 -->
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
@ -41,11 +41,13 @@
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone" />
<!-- 标题和时间显示区域,线性布局管理器,水平方向排列 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<!-- 便签标题,单行显示 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="0dip"
@ -53,6 +55,7 @@
android:layout_weight="1"
android:singleLine="true" />
<!-- 便签时间 -->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
@ -61,6 +64,7 @@
</LinearLayout>
</LinearLayout>
<!-- 复选框,通常不可见 -->
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
@ -70,17 +74,10 @@
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"/>
<ImageView
android:id="@+id/iv_top_icon"
android:layout_width="17dp"
android:layout_height="17dp"
android:layout_gravity="top|right"
android:layout_marginRight="40sp" />
android:layout_gravity="top|right" />
</FrameLayout>

@ -1,31 +1,23 @@
<?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.
<!--
此文件定义了一个FrameLayout布局用于显示一个带有标题栏和列表视图的应用界面。
标题栏可以隐藏,列表视图占据主要空间,并且底部有一个创建新笔记的按钮。
布局使用了线性布局和框架布局的组合来实现各个组件的定位和排列。
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background">
<!-- 线性布局垂直排列包含标题栏和ListView -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- 标题栏,可以隐藏,具有渐变背景色,文字居中 -->
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="fill_parent"
@ -37,22 +29,24 @@
android:textColor="#FFEAD1AE"
android:textSize="@dimen/text_font_size_medium" />
<!-- 列表视图,占据父容器剩余空间,无滚动条,无分隔线 -->
<ListView
android:id="@+id/notes_list"
android:layout_width="match_parent"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@null"
android:listSelector="@android:color/transparent" />
android:fadingEdge="@null" />
</LinearLayout>
<!-- 创建新笔记按钮,位于布局底部 -->
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/new_note"
android:focusable="false" />
android:focusable="false"
android:layout_gravity="bottom" />
</FrameLayout>

@ -1,28 +1,18 @@
<?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.
<!--
导入 Apache 2.0 许可证,指出文件的使用条件。
详细信息请访问 http://www.apache.org/licenses/LICENSE-2.0
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/navigation_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button android:id="@+id/selection_menu"
<Button
android:id="@+id/selection_menu"
android:divider="?android:attr/listDividerAlertDialog"
android:singleLine="true"
android:gravity="left|center_vertical"

@ -1,22 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此XML文件定义了一个视图(View)的布局属性。
版权归2010-2011年MiCode开源社区所有。
许可证Apache License, Version 2.0
获取许可证请访问http://www.apache.org/licenses/LICENSE-2.0
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.
该视图的属性包括:
- 布局宽度:填充父容器宽度
- 布局高度100dip
- 可见性:不可见
- 可聚焦:不可聚焦
- 背景:使用@drawable/list_footer_bg作为背景
-->
<View
xmlns:android="http://schemas.android.com/apk/res/android"
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="100dip"
android:visibility="invisible"

@ -1,26 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此XML文件定义了一个LinearLayout用于布局同步偏好设置的用户界面。
它包含一个按钮和一个文本视图,但文本视图默认是隐藏的。
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
版权声明2010-2011年MiCode开源社区。
许可证Apache Software License 2.0
详细信息请参见http://www.apache.org/licenses/LICENSE-2.0
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.
@author MiCode社区
-->
<LinearLayout
android:layout_width="fill_parent"
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 同步按钮:立即同步 -->
<Button
android:id="@+id/preference_sync_button"
android:layout_width="fill_parent"
@ -29,13 +25,14 @@
android:layout_marginLeft="30dip"
android:layout_marginRight="30dip"
style="?android:attr/textAppearanceMedium"
android:text="@string/preferences_button_sync_immediately"/>
android:text="@string/preferences_button_sync_immediately" />
<!-- 同步状态文本:默认隐藏 -->
<TextView
android:id="@+id/prefenerece_sync_status_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
android:visibility="gone" />
</LinearLayout>

@ -1,28 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
<!--
此XML文件定义了一个FrameLayout布局其中包含了一个ImageView和一个TextView。
布局尺寸为146dip x 146dip。ImageView用于显示背景图片而TextView用于展示文本信息。
此布局适用于Android平台通过指定Apache 2.0许可证,对使用、复制和修改等行为进行了规定。
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="146dip"
android:layout_height="146dip">
<!-- 定义ImageView用作布局的背景图片 -->
<ImageView
android:id="@+id/widget_bg_image"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<!-- 定义TextView用于显示文本信息设置了内边距、字体大小、颜色、最大行数和行间距 -->
<TextView
android:id="@+id/widget_text"
android:layout_width="fill_parent"

@ -1,30 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
<!--
此XML文件定义了一个FrameLayout布局其中包含了一个ImageView和一个TextView。
布局尺寸为294dip x 294dip。ImageView用于显示背景图片而TextView用于显示文本内容。
此布局适用于Android平台通过指定Apache 2.0许可证,对使用、复制和修改等行为进行了规定。
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="294dip"
android:layout_height="294dip">
<!-- 定义ImageView用作布局的背景图片 -->
<ImageView
android:id="@+id/widget_bg_image"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<!-- 定义TextView用于显示文本信息。通过设置padding、文字大小、颜色、最大行数等属性调整其显示效果。-->
<TextView
android:id="@+id/widget_text"
android:layout_width="fill_parent"

@ -1,52 +1,46 @@
<?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.
<!--
此文件定义了应用程序中一个菜单的布局。
菜单中包含了多个项目,每个项目对应一个功能。
具体功能通过项目对应的id和标题来定义。
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 删除功能的菜单项 -->
<item
android:id="@+id/menu_delete"
android:title="@string/menu_delete" />
<!-- 字体大小设置功能的菜单项 -->
<item
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"/>
<!-- 切换列表模式功能的菜单项 -->
<item
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode" />
<!-- 分享功能的菜单项 -->
<item
android:id="@+id/menu_share"
android:title="@string/menu_share"/>
<!-- 发送到桌面功能的菜单项 -->
<item
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"/>
<!-- 提醒功能的菜单项 -->
<item
android:id="@+id/menu_alert"
android:title="@string/menu_alert" />
<!-- 删除提醒功能的菜单项 -->
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
<item
android:id="@+id/menu_make_password"
android:title="@string/menu_set_password" />
</menu>

@ -1,23 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此文件定义了一个菜单资源用于在Android应用程序中显示一个搜索菜单项。
文件包含了一个<menu>元素,其中定义了一个<item>元素作为搜索菜单项。
此菜单项具有一个唯一的ID和一个显示的标题。
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
版权信息:
Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
许可证:
本文件发布于Apache License, Version 2.0下。详情请访问:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
限制:
除非适用法律要求或以书面形式同意,根据该许可证分发的软件都是按“原样”分发的,
不提供任何明示或暗示的保证。请参阅许可证文件了解特定的使用权限和限制。
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义一个菜单项,用于执行搜索操作 -->
<item
android:id="@+id/menu_search"
android:title="@string/menu_search" />
</menu>

@ -1,72 +1,51 @@
<?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.
<!--
此文件定义了应用程序的主菜单。
菜单选项包括创建新笔记、删除、调整字体大小、切换列表模式、分享、发送到桌面、提醒以及删除提醒。
版权归 MiCode 开源社区所有。
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 创建新笔记菜单项 -->
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
<item
android:id="@+id/menu_revoke"
android:title="@string/menu_revoke"/>
<item
android:id="@+id/menu_set_top"
android:title="@string/menu_set_top"/>
<item
android:id="@+id/menu_cancel_top"
android:title="@string/menu_cancel_top"/>
<!-- 删除菜单项 -->
<item
android:id="@+id/menu_delete"
android:title="@string/menu_delete"/>
<!-- 调整字体大小菜单项 -->
<item
android:id="@+id/menu_setFont_size"
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"/>
<!-- 切换列表模式菜单项 -->
<item
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode" />
<!-- 分享菜单项 -->
<item
android:id="@+id/menu_share"
android:title="@string/menu_share"/>
<!-- 发送到桌面菜单项 -->
<item
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"/>
<!-- 提醒菜单项 -->
<item
android:id="@+id/menu_alert"
android:title="@string/menu_alert" />
<!-- 删除提醒菜单项 -->
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
<item
android:id="@+id/menu_font_select"
android:title="@string/menu_font_style" />
<item
android:id="@+id/menu_count_word"
android:title="@string/menu_count_word" />
</menu>

@ -1,55 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此文件定义了应用程序的菜单资源。
它使用Android的菜单XML格式来描述应用主界面及其它界面中可能出现的菜单项。
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
Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
本文件受Apache License, Version 2.0保护;
除非符合许可条款,否则不得使用此文件。
您可以在以下地址获取许可副本:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
根据许可协议,除非法律要求或以书面形式同意,否则软件是按“原样”分发的,
不作任何明示或暗示的保证或条件。请参阅许可协议以了解具体权限和限制。
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 新建文件夹菜单项 -->
<item
android:id="@+id/menu_new_folder"
android:title="@string/menu_create_folder"/>
<!-- 导出文本菜单项 -->
<item
android:id="@+id/menu_export_text"
android:title="@string/menu_export_text"/>
<!-- 同步菜单项 -->
<item
android:id="@+id/menu_sync"
android:title="@string/menu_sync"/>
<!-- 设置菜单项 -->
<item
android:id="@+id/menu_setting"
android:title="@string/menu_setting" />
<!-- 搜索菜单项 -->
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"/>
<!-- qxq:Background image switching -->
<item
android:id="@+id/LoginSetting"
android:title="@string/LoginSetting"/>
android:id="@+id/menu_switch_to_picture1"
android:title="@string/menu_switch_to_picture1"/>
<item
android:id="@+id/delete_password"
android:title="@string/delete_password"/>
android:id="@+id/menu_switch_to_picture2"
android:title="@string/menu_switch_to_picture2"/>
<item
android:id="@+id/menu_switch_to_picture3"
android:title="@string/menu_switch_to_picture3"/>
<!-- qxq:LoginActivity -->
<item
android:id="@+id/menu_secret"
android:title="@string/menu_secret"/>
<item
android:id="@+id/menu_quit_secret"
android:title="@string/menu_quit_secret"/>
android:title="@string/menu_quit_secret" />
<item
android:id="@+id/menu_createlogin"
android:title="@string/menu_createlogin" />
<item
android:id="@+id/menu_deletelogin"
android:title="@string/menu_deletelogin" />
<item
android:id="@+id/menu_changelogin"
android:title="@string/menu_changelogin" />
</menu>

@ -1,20 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!--
此文件定义了一个菜单资源。
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.
此菜单资源受Apache License, Version 2.0保护。
你可以在http://www.apache.org/licenses/LICENSE-2.0获取许可证的副本。
除非依照适用法律或以书面形式同意,否则根据该许可证分发的软件应按“原样”分发,
不提供任何明示或暗示的保证。
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义一个菜单项,用于选择所有项 -->
<item android:id="@+id/action_select_all" android:title="@string/menu_select_all" />
</menu>

@ -1,28 +1,25 @@
<?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.
<!--
此文件定义了应用程序中菜单项的布局。
它使用Android的菜单XML格式来描述菜单项及其属性。
-->
<menu
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android">
<!--
定义了一个菜单项,用于移动操作。该菜单项始终显示在动作栏中,并显示文本标签。
菜单项的图标和标题通过引用资源来定义。
-->
<item
android:id="@+id/move"
android:title="@string/menu_move"
android:icon="@drawable/menu_move"
app:showAsAction="always|withText" />
android:showAsAction="always|withText" />
<!--
定义了一个菜单项,用于删除操作。和上面的移动操作类似,这个菜单项也始终显示在动作栏中,并带有文本标签。
-->
<item
android:id="@+id/delete"
android:title="@string/menu_delete"

@ -1,35 +1,27 @@
<?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
<!--
此文件定义了一个菜单资源,用于在应用程序中显示一个新的笔记选项。
版权归 MiCode 开源社区所有 (www.micode.net)
本文件受 Apache 2.0 许可证保护,关于许可证的详细信息,请访问:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
除非适用法律要求或以书面形式同意,根据该许可证分发的软件
是在“原样”基础上分发的,不作任何明示或暗示的保证。
有关许可证的具体条款和限制,请参阅许可证文件。
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义了一个菜单项,用于创建新的笔记 -->
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
<item
android:id="@+id/menu_key"
android:title="@string/notelist_menu_key"/>
android:title="@string/notelist_menu_new" />
<!--qxq 定义了一个 -->
<item
android:id="@+id/menu_secret"
android:title="@string/menu_secret"/>
<item
android:id="@+id/menu_quit_secret"
android:title="@string/menu_quit_secret"/>

@ -1,23 +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.
-->
<resources>
<string-array name="menu_share_ways">
<item>短信</item>
<item>邮件</item>
</string-array>
</resources>

@ -1,140 +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.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">메일</string>
<string name="menu_count_word">단어 수를 계산합니다</string>
<string name="menu_set_passcode">암호를 설정하세요</string>
<string name="menu_delete_passcode">비밀번호 삭제</string>
<string name="menu_secret">개인 정보 보호 모드</string>
<string name="menu_quit_secret">비공개 모드를 종료합니다</string>
<string name="app_widget2x2">메일 2x2</string>
<string name="app_widget4x4">메일 4x4</string>
<string name="widget_havenot_content">연합되는 주를 창조하기 위하여 찾아내는 연합되는 주 없음, 누르기.</string>
<string name="widget_under_visit_mode">기밀 형태는, 주 내용을 볼 수 없다</string>
<string name="notelist_string_info">...</string>
<string name="notelist_menu_new">새 메모</string>
<string name="delete_remind_time_message">미리 알림 성공적으로 삭제</string>
<string name="set_remind_time_message">알람 생성</string>
<string name="note_alert_expired">기간이 만료됨</string>
<string name="format_date_ymd">yyyyMMdd</string>
<string name="format_datetime_mdhm">MMMd kk:mm</string>
<string name="notealert_ok">알겠어요</string>
<string name="notealert_enter">보기</string>
<string name="note_link_tel">호출 전화</string>
<string name="note_link_email">전자 우편 보내기</string>
<string name="note_link_web">웹 페이지 찾아보기</string>
<string name="note_link_other">지도 열기</string>
<!-- Text export file information -->
<string name="file_path">/MIUI/notes/</string>
<string name="file_name_txt_format">notes_%s.txt</string>
<!-- notes list string -->
<string name="format_folder_files_count">(%d)</string>
<string name="menu_create_folder">새 폴더 만들기</string>
<string name="menu_export_text">텍스트 내보내기</string>
<string name="menu_sync">동기화</string>
<string name="menu_sync_cancel">동기화 해제</string>
<string name="menu_setting">설정</string>
<string name="menu_search">검색</string>
<string name="menu_delete">삭제</string>
<string name="menu_move">편지함으로 이동</string>
<string name="menu_select_title">%d 항목 선택됨</string>
<string name="menu_select_none">선택된 항목이 없습니다. 작업이 올바르지 않습니다</string>
<string name="menu_select_all">모두 선택</string>
<string name="menu_deselect_all">모두 선택 해제</string>
<string name="menu_font_size">텍스트 크기</string>
<string name="menu_font_small">작은</string>
<string name="menu_font_normal">가운데</string>
<string name="menu_font_large"></string>
<string name="menu_font_super">초대</string>
<string name="menu_list_mode">상세한 명부 본에 들어간다</string>
<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="folder_exist">폴더%1 s이 (가) 이미 존재합니다. 이름을 바꾸십시오.</string>
<string name="menu_share">나누기</string>
<string name="menu_send_to_desktop">데스크톱으로 보내기</string>
<string name="menu_alert">깨우쳐 주다</string>
<string name="menu_remove_remind">알람 삭제</string>
<string name="menu_title_select_folder">편지함 선택</string>
<string name="menu_move_parent_folder">상위 디렉토리r</string>
<string name="info_note_enter_desktop">데스크톱에 추가됨</string>
<string name="alert_message_delete_folder">폴더 및 그 아래에 있는 메모를 삭제하시겠습니까?</string>
<string name="alert_title_delete">삭제</string>
<string name="alert_message_delete_notes">선택한%d 노트를 삭제하시겠습니까?</string>
<string name="alert_message_delete_note">이 메모 지우시겠습니까?</string>
<string name="format_move_notes_to_folder">선택한%1달러 d 노트북은%2달러 s 폴더에 옮겨졌습니다</string>
<!-- Error information -->
<string name="error_sdcard_unmounted">SD 카드가 사용되어 운영되지 않고 있습니다</string>
<string name="error_sdcard_export">텍스트를 내보낼 때 오류가 발생했습니다. SD 카드를 확인하십시오.</string>
<string name="error_note_not_exist">확인할 노트가 없습니다</string>
<string name="error_note_empty_for_clock">빈 노트북에 알람을 설정할 수 없습니다</string>
<string name="error_note_empty_for_send_to_desktop">데스크톱에 빈 메모 보낼 수 없음</string>
<string name="success_sdcard_export">내보내기 성공</string>
<string name="failed_sdcard_export">내보내기 실패</string>
<string name="format_exported_file_location">텍스트 파일 (%1$s)을 SD 카드에서 출력했습니다 (%2$s) 디렉터리로</string>
<!-- Sync -->
<string name="ticker_syncing">동기화 메모 …</string>
<string name="ticker_success">동기화 성공</string>
<string name="ticker_fail">동기화 실패</string>
<string name="ticker_cancel">동기화 해제</string>
<string name="success_sync_account">%1$s 동기화 성공</string>
<string name="error_sync_network">동기화에 실패했습니다. 네트워크와 계정 설정을 확인하십시오.</string>
<string name="error_sync_internal">동기화 실패, 내부 오류 발생</string>
<string name="error_sync_cancelled">동기화 해제</string>
<string name="sync_progress_login">로그인%1$s …</string>
<string name="sync_progress_init_list">서버 메모 목록 가져오는 중 …</string>
<string name="sync_progress_syncing">로컬 메모 동기화 중 …</string>
<!-- Preferences -->
<string name="preferences_title">설정</string>
<string name="preferences_account_title">동기화 계정</string>
<string name="preferences_account_summary">google task와 나란히 메모</string>
<string name="preferences_last_sync_time">지난 번에%1$s 동기화</string>
<string name="preferences_last_sync_time_format">yyyy-MM-dd hh:mm:ss</string>
<string name="preferences_add_account">계좌 번호 추가</string>
<string name="preferences_menu_change_account">계좌 번호를 바꾸다</string>
<string name="preferences_menu_remove_account">계정 삭제</string>
<string name="preferences_menu_cancel">취소</string>
<string name="preferences_button_sync_immediately">지금 동기화</string>
<string name="preferences_button_sync_cancel">동기화 해제</string>
<string name="preferences_dialog_change_account_title">커런트 계정%1$s</string>
<string name="preferences_dialog_change_account_warn_msg">동기식 계정 변경시 과거 계정 동기식 정보가 지워지므로 재전환시 데이터가 중복될 수 있습니다.</string>
<string name="preferences_dialog_select_account_title">동기화 메모</string>
<string name="preferences_dialog_select_account_tips">이 계정의 구글 작업 내용과 동기화하기 위해 구글 계정을 선택하십시오</string>
<string name="preferences_toast_cannot_change_account">동기화하는 중 계정 정보를 수정할 수 없습니다.</string>
<string name="preferences_toast_success_set_accout">계정이%1$s로 설정되었습니다.</string>
<string name="preferences_bg_random_appear_title">새 메모 배경 색상 임의적으로</string>
<string name="button_delete">삭제</string>
<string name="call_record_folder_name">통화 메모</string>
<string name="hint_foler_name">이름을 입력하십시오.</string>
<string name="search_label">메모 찾는 중</string>
<string name="search_hint">메모 찾기</string>
<string name="search_setting_description">메모의 글</string>
<string name="search">메모</string>
<string name="datetime_dialog_ok">설정</string>
<string name="datetime_dialog_cancel">취소</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>
<!-- Case of 0 or 2 or more results. -->
<item quantity="other"><xliff:g id="NUMBER">%1$s</xliff:g> 가닥입니다“<xliff:g id="SEARCH">%2$s</xliff:g>” 검색 결과에 부합하는</item>
</plurals>
</resources>

@ -0,0 +1,3 @@
<resources>
<dimen name="activity_horizontal_margin">48dp</dimen>
</resources>

@ -0,0 +1,7 @@
<resources>
<style name="ThemeOverlay.Notes.FullscreenContainer" parent="">
<item name="fullscreenBackgroundColor">@color/light_blue_900</item>
<item name="fullscreenTextColor">@color/light_blue_A400</item>
</style>
</resources>

@ -0,0 +1,3 @@
<resources>
<dimen name="activity_horizontal_margin">200dp</dimen>
</resources>

@ -0,0 +1,3 @@
<resources>
<dimen name="activity_horizontal_margin">48dp</dimen>
</resources>

@ -1,21 +1,19 @@
<?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
<!--
此文件为资源字符串数组定义,用于定义应用中分享菜单的选项。
版权归 MiCode 开源社区所有 (www.micode.net)
使用 Apache 2.0 许可证进行授权,详情请访问:
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.
除非法律要求或以书面形式同意,根据该许可证分发的软件
是在“原样”基础上分发的,不提供任何明示或暗示的保证。
请参阅许可证文件了解特定的使用权限和限制。
-->
<resources>
<!-- 定义分享方式的字符串数组 -->
<string-array name="menu_share_ways">
<item>短信</item>
<item>邮件</item>

@ -24,9 +24,6 @@
<string name="widget_under_visit_mode">访客模式下,便签内容不可见</string>
<string name="notelist_string_info">...</string>
<string name="notelist_menu_new">新建便签</string>
<string name="menu_revoke">撤销</string>
<string name="menu_set_top">置顶签</string>
<string name="menu_cancel_top">取消置顶</string>
<string name="delete_remind_time_message">成功删除提醒</string>
<string name="set_remind_time_message">创建提醒</string>
<string name="note_alert_expired">已过期</string>
@ -45,9 +42,6 @@
<string name="menu_sync_cancel">取消同步</string>
<string name="menu_setting">设置</string>
<string name="menu_search">搜索</string>
<string name="LoginSetting">设置登陆密码</string>
<string name="delete_password">删除登陆密码</string>
<string name="menu_delete">删除</string>
<string name="menu_move">移动到文件夹</string>
<string name="menu_select_title">选中了 %d 项</string>
@ -69,15 +63,10 @@
<string name="menu_send_to_desktop">发送到桌面</string>
<string name="menu_alert">提醒我</string>
<string name="menu_remove_remind">删除提醒</string>
<string name="menu_font_style">设置字体</string>
<string name="menu_count_word">统计字数</string>
<string name="menu_set_password">删设置密码</string>
<string name="menu_title_select_folder">选择文件夹</string>
<string name="menu_move_parent_folder">上一级文件夹</string>
<string name="info_note_enter_desktop">已添加到桌面</string>
<string name="alert_title_delete">删除</string>
<string name="menu_secret">私密模式</string>
<string name="menu_quit_secret">退出私密模式</string>
<string name="alert_message_delete_notes">确认要删除所选的 %d 条便签吗?</string>
<string name="alert_message_delete_note">确认要删除该条便签吗?</string>
<string name="alert_message_delete_folder">确认删除文件夹及所包含的便签吗?</string>
@ -86,7 +75,6 @@
<string name="error_sdcard_unmounted">SD卡被占用不能操作</string>
<string name="error_sdcard_export">导出文本时发生错误请检查SD卡</string>
<string name="error_note_not_exist">要查看的便签不存在</string>
<string name="error_note_empty_for_top">不能置顶空便签</string>
<string name="error_note_empty_for_clock">不能为空便签设置闹钟提醒</string>
<string name="error_note_empty_for_send_to_desktop">不能将空便签发送到桌面</string>
<string name="success_sdcard_export">导出成功</string>
@ -131,12 +119,6 @@
<string name="search">便签</string>
<string name="datetime_dialog_ok">设置</string>
<string name="datetime_dialog_cancel">取消</string>
<string name="notelist_menu_key" >关键</string>
<string name="dummy_content" >记录生活</string>
<string name="dummy_button" >DUMMY_BUTTON</string>
<string name="tips_of_revoke">提示</string>
<string name="can_not_revoke">您不能再执行撤销了</string>
<string name="have_not_input_anything">您还没有输入任何内容</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>

@ -1,21 +1,19 @@
<?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
<!--
此文件为资源字符串数组定义,用于提供应用中分享菜单的选项。
版权归 MiCode 开源社区所有 (www.micode.net)
使用 Apache 2.0 许可证进行授权,详情请访问:
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.
除非法律要求或以书面形式同意,根据该许可证分发的软件
是在“原样”基础上分发的,不提供任何明示或暗示的保证。
请参阅许可证文件了解具体权限和限制。
-->
<resources>
<!-- 定义分享方式的字符串数组 -->
<string-array name="menu_share_ways">
<item>短信</item>
<item>郵件</item>

@ -24,9 +24,6 @@
<string name="widget_under_visit_mode">訪客模式下,便籤內容不可見</string>
<string name="notelist_string_info">...</string>
<string name="notelist_menu_new">新建便簽</string>
<string name="menu_revoke">撤销</string>
<string name="menu_set_top">置顶</string>
<string name="menu_cancel_top">取消置顶</string>
<string name="delete_remind_time_message">成功刪除提醒</string>
<string name="set_remind_time_message">創建提醒</string>
<string name="note_alert_expired">已過期</string>
@ -46,8 +43,6 @@
<string name="menu_sync_cancel">取消同步</string>
<string name="menu_setting">設置</string>
<string name="menu_search">搜尋</string>
<string name="LoginSetting">设置登陆密码</string>
<string name="delete_password">删除登陆密码</string>
<string name="menu_delete">刪除</string>
<string name="menu_move">移動到文件夾</string>
<string name="menu_select_title">選中了 %d 項</string>
@ -69,22 +64,16 @@
<string name="menu_send_to_desktop">發送到桌面</string>
<string name="menu_alert">提醒我</string>
<string name="menu_remove_remind">刪除提醒</string>
<string name="menu_font_style">设置字体</string>
<string name="menu_count_word">统计字数</string>
<string name="menu_set_password">设置密码</string>
<string name="menu_title_select_folder">選擇文件夾</string>
<string name="menu_move_parent_folder">上一級文件夾</string>
<string name="info_note_enter_desktop">已添加到桌面</string>
<string name="alert_title_delete">刪除</string>
<string name="menu_secret">私密模式</string>
<string name="menu_quit_secret">退出私密模式</string>
<string name="alert_message_delete_notes">确认要刪除所選的 %d 條便籤嗎?</string>
<string name="alert_message_delete_note">确认要删除該條便籤嗎?</string>
<string name="alert_message_delete_folder">確認刪除檔夾及所包含的便簽嗎?</string>
<string name="error_sdcard_unmounted">SD卡被佔用不能操作</string>
<string name="error_sdcard_export">導出TXT時發生錯誤請檢查SD卡</string>
<string name="error_note_not_exist">要查看的便籤不存在</string>
<string name="error_note_empty_for_top">不能置顶空便籤</string>
<string name="error_note_empty_for_clock">不能爲空便籤設置鬧鐘提醒</string>
<string name="error_note_empty_for_send_to_desktop">不能將空便籤發送到桌面</string>
<string name="success_sdcard_export">導出成功</string>
@ -131,12 +120,6 @@
<string name="search">便籤</string>
<string name="datetime_dialog_ok">設置</string>
<string name="datetime_dialog_cancel">取消</string>
<string name="notelist_menu_key" >关键</string>
<string name="dummy_content" >记录生活</string>
<string name="dummy_button" >DUMMY_BUTTON</string>
<string name="tips_of_revoke">提示</string>
<string name="can_not_revoke">您不能再执行撤销了</string>
<string name="have_not_input_anything">您还没有输入任何内容</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>

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

Loading…
Cancel
Save