Task & TaksList & ActionFailureException类的注释 #17

Open
pp6wf43ns wants to merge 0 commits from hy_branch into master

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

@ -86,4 +86,4 @@ public class Contact {
}
}
}
/* 这个类主要用于根据电话号码查询联系人名称。在查询过程中会将查询到的联系人名称缓存在一个HashMap中以便于下次查询时直接从缓存中获取。*/
/*这个类主要用于根据电话号码查询联系人名称。在查询过程中会将查询到的联系人名称缓存在一个HashMap中以便于下次查询时直接从缓存中获取。*/

@ -17,49 +17,50 @@
package net.micode.notes.data;
import android.net.Uri;
// 定义“笔记”类
public class Notes {
public static final String AUTHORITY = "micode_notes";
public static final String TAG = "Notes";
// 定义笔记类型常量
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
/**
* ID
* {@link Notes#ID_ROOT_FOLDER }
* {@link Notes#ID_TEMPARAY_FOLDER }
* {@link Notes#ID_CALL_RECORD_FOLDER}
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;
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;
// 定义意图(extra)的键
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
// 定义小部件的类型常量
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
// 定义数据常量
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
// 定义笔记的uri可用于查询所有笔记和文件夹
/**
* Uri to query all notes and folders
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
// 定义数据的uri
/**
* Uri to query data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// 定义笔记和文件夹的列名
public interface NoteColumns {
/**
* The unique ID for a row
@ -165,7 +166,7 @@ public class Notes {
*/
public static final String VERSION = "version";
}
// 定义数据表的列名
public interface DataColumns {
/**
* The unique ID for a row
@ -239,7 +240,7 @@ public class Notes {
*/
public static final String DATA5 = "data5";
}
// 定义文本笔记类
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
@ -255,7 +256,7 @@ public class Notes {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
// 定义通话笔记类
public static final class CallNote implements DataColumns {
/**
* Call date for this record

@ -26,22 +26,22 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
// 定义一个名为NotesDatabaseHelper的公共类继承自SQLiteOpenHelper
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";
// 定义一个NotesDatabaseHelper类型的私有静态实例变量
private static NotesDatabaseHelper mInstance;
// 定义创建note表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
@ -85,7 +85,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Increase folder's note count when move note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
@ -98,7 +97,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Decrease folder's note count when move note from folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
@ -112,7 +110,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Increase folder's note count when insert new note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
@ -138,7 +135,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Update note's content when insert data with type {@link DataConstants#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 +
@ -152,7 +148,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
// 定义更新笔记内容的触发器数据类型为NOTE时发生变化
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +

@ -16,6 +16,7 @@
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
@ -33,21 +34,21 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider {
// 声明 UriMatcher用于匹配 Uri 和对应的操作
private static final UriMatcher mMatcher;
private NotesDatabaseHelper mHelper;
private static final String TAG = "NotesProvider";
// 声明 UriMatcher用于匹配 Uri 和对应的操作
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
private static final int URI_DATA_ITEM = 4;
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
private static final int URI_DATA_ITEM = 4;
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@ -61,33 +62,29 @@ public class NotesProvider extends ContentProvider {
}
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the
* search result,
* 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.
*/
// 声明 UriMatcher用于匹配 Uri 和对应的操作
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
// 声明常量,表示查询搜索建议的 SQL 语句
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
// 初始化 NotesProvider
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
// 查询数据
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
@ -230,7 +227,6 @@ public class NotesProvider extends ContentProvider {
return count;
}
// 实现了更新数据的功能
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
@ -275,16 +271,6 @@ public class NotesProvider extends ContentProvider {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
/**
*
* NoteNote1
*
* @param id NoteID-1Note
*
* @param selection
*
* @param selectionArgs
**/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");

@ -19,17 +19,14 @@ package net.micode.notes.gtask.exception;
public class NetworkFailureException extends Exception {
private static final long serialVersionUID = 2107610287180234136L;
// 无参构造函数
public NetworkFailureException() {
super();
}
// 构造函数,接受一个字符串消息作为参数
public NetworkFailureException(String paramString) {
super(paramString);
}
// 构造函数,接受一个字符串消息和一个可抛出对象作为参数
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}

@ -0,0 +1,916 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
// 定义一个GTaskManager类
public class GTaskManager {
// 声明一个TAG常量用于记录日志
private static final String TAG = GTaskManager.class.getSimpleName();
// 声明四个int类型的状态常量
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、Context、ContentResolver等变量
private Activity mActivity;
private Context mContext;
private ContentResolver mContentResolver;
// 声明标记同步状态和取消同步状态的变量
private boolean mSyncing;
private boolean mCancelled;
// 声明三个HashMap一个TaskList对象一个HashSet和两个映射表
private HashMap<String, TaskList> mGTaskListHashMap;
private HashMap<String, Node> mGTaskHashMap;
private HashMap<String, MetaData> mMetaHashMap;
private TaskList mMetaList;
private HashSet<Long> mLocalDeleteIdMap;
private HashMap<String, Long> mGidToNid;
private HashMap<Long, String> mNidToGid;
// 构造函数,初始化上述变量
private GTaskManager() {
mSyncing = false;
mCancelled = false;
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
// 获取GTaskManager实例的静态方法
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
// 设置Activity上下文的方法
public synchronized void setActivityContext(Activity activity) {
// 用于获取authToken
mActivity = activity;
}
}
public int sync(Context context, GTaskASyncTask asyncTask) {
// 检查是否正在同步任务,如果是,则返回“正在同步”的状态
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
// 初始化变量
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
// 获取GTaskClient的实例
GTaskClient client = GTaskClient.getInstance();
// 重置更新数组
client.resetUpdateArray();
// 登录Google任务
if (!mCancelled) {
if (!client.login(mActivity)) {
// 登录失败,抛出异常
throw new NetworkFailureException("login google task failed");
}
}
// 从Google获取任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 进行内容同步工作
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
// 网络连接失败,返回网络错误状态
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
// 执行任务失败,返回内部错误状态
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
// 发生其他异常,打印异常信息并返回内部错误状态
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
// 清空所有变量,标记同步完成
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
// 返回同步是否被取消的状态
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
private void initGTaskList() throws NetworkFailureException {
// 检查任务是否已被取消
if (mCancelled)
return;
// 获取 GTaskClient 实例
GTaskClient client = GTaskClient.getInstance();
try {
// 获取任务列表的 JSON 数组
JSONArray jsTaskLists = client.getTaskLists();
// 初始化元列表
mMetaList = null;
// 遍历任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
// 获取任务列表中的 JSON 对象
JSONObject object = jsTaskLists.getJSONObject(i);
// 获取任务列表的 ID 和名称
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();
// 将元列表的内容设置为远程 JSON 数据
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
metaData.setContentByRemoteJSON(object);
if (metaData.isWorthSaving()) {
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// 如果元列表不存在,则创建元列表
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 如果任务列表名称以 MIUI_FOLDER_PREFFIX 开头且不是元列表,则初始化任务列表
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
TaskList tasklist = new TaskList();
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// 加载任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid));
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
pprivate void syncContent() throws NetworkFailureException {
int syncType; // 同步类型
Cursor c = null; // 游标对象
String gid; // GTasks ID
Node node; // 节点对象
mLocalDeleteIdMap.clear(); // 清空本地删除ID Map
if (mCancelled) { // 如果已取消,则返回
return;
}
// 处理本地已删除的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid); // 获取对应的节点对象
if (node != null) {
mGTaskHashMap.remove(gid); // 从 HashMap 中移除对应的节点
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行同步
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 将本地删除 ID 加入 Map 中
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 先同步文件夹
syncFolder();
// 处理存在于数据库中的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid); // 获取对应的节点对象
if (node != null) {
mGTaskHashMap.remove(gid); // 从 HashMap 中移除对应的节点
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 将 GTasks ID 与本地 ID 对应
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 将本地 ID 与 GTasks ID 对应
syncType = node.getSyncAction(c); // 获取同步类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地新增
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c); // 执行同步
}
} else {
Log.w(TAG, "failed to query existing note in database");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理剩余的项目
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// mCancelled can be set by another thread, so we neet to check one by
// one
// clear local delete table
// 检查操作是否已取消,如果未取消则继续执行
if (!mCancelled) {
// 如果没有取消且无法批量删除本地已删除的笔记,则抛出一个异常
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// 如果操作未取消,则执行以下操作
if (!mCancelled) {
// 更新 GTaskClient 实例,并刷新本地同步 ID
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
// 如果任务已经取消了,就直接退出
if (mCancelled) {
return;
}
// 处理根文件夹
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null) {
c.moveToNext();
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
} else {
Log.w(TAG, "failed to query root folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理通话记录文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
}
} else {
Log.w(TAG, "failed to query call note folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理本地现有文件夹
try {
// 查询note表中非垃圾桶下的所有笔记并按类型降序排列
// 查询条件为类型为folder且父文件夹不为垃圾桶
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
// 如果查询结果非空
if (c != null) {
while (c.moveToNext()) {
// 获取笔记的GTasks ID
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 从GTasks哈希表中获取笔记节点
node = mGTaskHashMap.get(gid);
// 如果节点非空
if (node != null) {
// 从哈希表中移除该节点
mGTaskHashMap.remove(gid);
// 将该笔记的GTasks ID与本地ID建立映射关系
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
// 将本地ID与该笔记的GTasks ID建立映射关系
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
// 获取节点的同步操作类型
syncType = node.getSyncAction(c);
} else {
// 如果该笔记在GTasks中不存在
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地新增
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
// 执行内容同步
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing folder");
}
} finally {
// 关闭游标
if (c != null) {
c.close();
c = null;
}
}
// 遍历GTasks列表哈希表对于每一个远程新增的文件夹执行本地新增操作
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
// 提交更新操作
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
/**
*
*
* @param syncType
* @param node
* @param c
* @throws NetworkFailureException NetworkFailureException
*/
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
// 如果已经取消了同步,则直接返回。
if (mCancelled) {
return;
}
MetaData meta;
// 根据不同的同步操作类型,执行不同的同步操作。
switch (syncType) {
case Node.SYNC_ACTION_ADD_LOCAL:
// 将本地节点添加到云端。
addLocalNode(node);
break;
case Node.SYNC_ACTION_ADD_REMOTE:
// 将云端节点添加到本地。
addRemoteNode(node, c);
break;
case Node.SYNC_ACTION_DEL_LOCAL:
// 删除本地节点。
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
case Node.SYNC_ACTION_DEL_REMOTE:
// 删除云端节点。
meta = mMetaHashMap.get(node.getGid());
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
GTaskClient.getInstance().deleteNode(node);
break;
case Node.SYNC_ACTION_UPDATE_LOCAL:
// 更新本地节点。
updateLocalNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_REMOTE:
// 更新云端节点。
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// 合并本地和云端的修改。
// 目前只是简单地使用本地的修改。
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
// 没有任何同步操作需要执行。
break;
case Node.SYNC_ACTION_ERROR:
default:
// 同步操作类型未知,抛出 ActionFailureException 异常。
throw new ActionFailureException("unkown sync action type");
}
}
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) { // 检查任务是否被取消,如果被取消,则返回
return;
}
SqlNote sqlNote; // 声明SqlNote对象
if (node instanceof TaskList) { // 如果节点为任务列表
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { // 如果节点名称是默认文件夹
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); // 设置SqlNote对象的mId为root_folder
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { // 如果节点名称是通话记录文件夹
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); // 设置SqlNote对象的mId为call_record_folder
} else {
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent()); // 设置SqlNote对象的内容为节点的JSON内容
sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 设置SqlNote对象的父节点ID为root_folder
}
} else { // 如果节点不是任务列表
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent(); // 获取节点的JSON内容
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { // 如果JSON内容中包含“meta_head_note”字段
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取“meta_head_note”字段对应的JSONObject
if (note.has(NoteColumns.ID)) { // 如果JSONObject中包含“id”字段
long id = note.getLong(NoteColumns.ID); // 获取“id”字段对应的值
if (DataUtils.existInNoteDatabase(mContentResolver, id)) { // 如果本地数据库中已经存在该“id”
// the id is not available, have to create a new one
note.remove(NoteColumns.ID); // 则删除该“id”字段
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) { // 如果JSON内容中包含“meta_head_data”字段
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取“meta_head_data”字段对应的JSONArray
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i); // 获取JSONArray中对应位置的JSONObject
if (data.has(DataColumns.ID)) { // 如果JSONObject中包含“id”字段
long dataId = data.getLong(DataColumns.ID); // 获取“id”字段对应的值
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { // 如果本地数据库中已经存在该“id”
// the data id is not available, have to create a new one
data.remove(DataColumns.ID); // 则删除该“id”字段
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js); // 设置SqlNote对象的内容为节点的JSON内容
// 从node对象中获取其父任务的gid再根据gid到本地gid-nid映射表中查找其对应的nid
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
// 如果nid为null则打印日志并抛出ActionFailureException异常
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
// 将获取到的nid设置为sqlNote的parentId属性值
sqlNote.setParentId(parentId.longValue());
// 将node的gid设置为sqlNote的GtaskId属性值
sqlNote.setGtaskId(node.getGid());
// 提交sqlNote的修改到数据库中但不包括提交事务
sqlNote.commit(false);
// 将node的gid和sqlNote的id映射关系加入本地gid-nid和nid-gid映射表中
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// 更新远程meta数据
updateRemoteMeta(node.getGid(), sqlNote);
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// 将节点的内容从JSON字符串转换为本地数据库中的SqlNote对象
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
// 获取节点的父节点ID
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
// 如果无法找到父节点ID则抛出ActionFailureException异常
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
// 将SqlNote对象的父ID设置为获取到的父节点ID
sqlNote.setParentId(parentId.longValue());
// 将SqlNote对象保存到本地数据库中
sqlNote.commit(true);
// 更新远程节点的元数据信息
updateRemoteMeta(node.getGid(), sqlNote);
}
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
// 如果任务已被取消,则直接返回,不执行任务
if (mCancelled) {
return;
}
// 创建 SqlNote 对象
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// 如果是任务类型的注释
if (sqlNote.isNoteType()) {
// 创建 Task 对象
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
// 获取任务的父 ID并创建父任务列表的 GID 映射
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
// 如果无法找到任务的父任务列表,则抛出异常
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task");
}
// 将任务添加到父任务列表中
mGTaskListHashMap.get(parentGid).addChildTask(task);
// 使用 GTaskClient 创建任务
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// 更新任务的元数据
updateRemoteMeta(task.getGid(), sqlNote);
} else {
// 如果是任务列表类型的注释
TaskList tasklist = null;
// 设置文件夹名
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT;
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
else
folderName += sqlNote.getSnippet();
// 遍历已存在的任务列表
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
// 如果任务列表的名称与文件夹名称匹配,则将任务列表设置为当前列表
if (list.getName().equals(folderName)) {
tasklist = list;
// 如果任务列表的 GID 已经存在,则从 mGTaskHashMap 中移除该 GID
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// 如果任务列表不存在,则创建新任务列表并添加到 mGTaskListHashMap
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
}
// 更新本地 SqlNote 对象的 GID并提交更改
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// 添加 GID 和 ID 的映射
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { // 如果任务被取消,停止更新远程节点
return;
}
// 创建 SqlNote 对象
SqlNote sqlNote = new SqlNote(mContext, c);
// 更新远程节点
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote);
// 如果是 Note 类型,移动任务
if (sqlNote.isNoteType()) {
Task task = (Task) node; // 将节点转换为 Task 类型
TaskList preParentList = task.getParent(); // 获取任务的旧父任务列表
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); // 获取新父任务列表
if (preParentList != curParentList) { // 如果任务的父任务列表发生了变化
preParentList.removeChildTask(task); // 在旧的父任务列表中移除该任务
curParentList.addChildTask(task); // 在新的父任务列表中添加该任务
GTaskClient.getInstance().moveTask(task, preParentList, curParentList); // 在 Google 任务中移动任务
}
}
// 清除本地修改标志
sqlNote.resetLocalModified();
sqlNote.commit(true); // 提交更改
}
/**
*
*
* @param gid GTask ID
* @param sqlNote SQL
* @throws NetworkFailureException
*/
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
// 检查是否为笔记类型
if (sqlNote != null && sqlNote.isNoteType()) {
// 获取指定 gid 的 MetaData 对象
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
// 更新 MetaData 对象中指定 gid 的元数据
metaData.setMeta(gid, sqlNote.getContent());
// 向 GTask 服务器提交更新请求
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
// 如果 MetaData 对象不存在,则新建 MetaData 对象
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
// 添加 MetaData 对象到 MetaList 中
mMetaList.addChildTask(metaData);
// 将 MetaData 对象存储到 HashMap 中
mMetaHashMap.put(gid, metaData);
// 向 GTask 服务器提交创建请求
GTaskClient.getInstance().createTask(metaData);
}
}
}
/**
* ID
*
* @throws NetworkFailureException
*/
private void refreshLocalSyncId() throws NetworkFailureException {
// 检查是否已取消同步
if (mCancelled) {
return;
}
// 清空哈希表
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
// 初始化 GTaskList
initGTaskList();
Cursor c = null;
try {
// 查询本地未被删除且非系统笔记的 SQL 数据对象
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 获取指定 gid 的 GTask 任务节点
Node node = mGTaskHashMap.get(gid);
if (node != null) {
// 移除哈希表中的 GTask 任务节点
mGTaskHashMap.remove(gid);
ContentValues values = new ContentValues();
// 更新本地 SQL 数据对象的同步 ID
values.put(NoteColumns.SYNC_ID, node.getLastModified());
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
Log.e(TAG, "something is missed");
// 抛出 ActionFailureException 异常
throw new ActionFailureException(
"some local items don't have gid after sync");
}
}
} else {
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
}
/**
*
* @return
*/
public String getSyncAccount() {
// 通过调用 GTaskClient 类的 getInstance() 方法获取 GTaskClient 实例,并调用该实例的 getSyncAccount() 方法,获取同步账户
return GTaskClient.getInstance().getSyncAccount().name;
}
/**
*
*/
public void cancelSync() {
// 将 mCancelled 标志设置为 true表示取消同步
mCancelled = true;
}
}

@ -24,7 +24,6 @@ import android.os.Bundle;
import android.os.IBinder;
public class GTaskSyncService extends Service {
// 同步动作的广播名称ACTION_START_SYNC 表示开始同步ACTION_CANCEL_SYNC 表示取消同步ACTION_INVALID 表示无效
public final static String ACTION_STRING_NAME = "sync_action_type";
public final static int ACTION_START_SYNC = 0;
@ -32,17 +31,17 @@ public class GTaskSyncService extends Service {
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() {
@ -56,7 +55,7 @@ public class GTaskSyncService extends Service {
mSyncTask.execute();
}
}
// 取消同步
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
@ -93,11 +92,11 @@ public class GTaskSyncService extends Service {
mSyncTask.cancelSync();
}
}
// 不支持绑定
public IBinder onBind(Intent intent) {
return null;
}
// 发送同步进度广播
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
@ -105,25 +104,25 @@ public class GTaskSyncService extends Service {
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
sendBroadcast(intent);
}
// 静态方法,开始同步任务
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
// 静态方法,取消同步任务
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);// 创建一个新的意图Intent指向GTaskSyncService类传入context参数
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);// 给Intent添加一个ACTION_CANCEL_SYNC的字符串Extra参数
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
public static boolean isSyncing() {// 定义一个公共的静态方法isSyncing返回值为布尔类型
public static boolean isSyncing() {
return mSyncTask != null;
}
public static String getProgressString() {// 定义一个公共的静态方法getProgressString返回值为字符串类型
public static String getProgressString() {
return mSyncProgress;
}
}

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

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

@ -0,0 +1,344 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
public int exportToText() {
return mTextExport.exportToText();
}
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
private final String [] TEXT_FORMAT;
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
private Context mContext;
private String mFileName;
private String mFileDirectory;
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// print a line separator between note
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try {
if (!filedir.exists()) {
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

@ -37,16 +37,7 @@ import java.util.HashSet;
public class DataUtils {
public static final String TAG = "DataUtils";
/**
*
*
* @param resolver ContentResolver
* @param ids HashSet<Long>id
* @return boolean
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
// 如果ids为空或者size为0则直接返回true
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
@ -56,119 +47,74 @@ public class DataUtils {
return true;
}
// 创建操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
// 不包括系统文件夹根节点
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
// 创建ContentProviderOperation.Builder对象来构建删除该id的操作并将其添加到operationList中
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
}
try {
// 通过ContentResolver的applyBatch()方法将所有操作提交给ContentProvider执行
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;
}
// 如果成功则返回true
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
// 失败则返回false
return false;
}
/**
*
*
* @param resolver ContentResolver
* @param id id
* @param srcFolderId id
* @param desFolderId id
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
// 创建ContentValues对象用于存放需要更新的字段和值
ContentValues values = new ContentValues();
// 更新Parent_Id字段为目标文件夹id
values.put(NoteColumns.PARENT_ID, desFolderId);
// 更新Origin_Parent_Id字段为原文件夹id
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
// 更新Local_Modified字段为1
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 调用ContentResolver的update()方法来更新数据
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
/**
*
*
* @param resolver ContentResolver
* @param ids HashSet<Long>id
* @param folderId id
* @return boolean
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids, long folderId) {
// 如果ids为空则直接返回true
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
// 创建操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
// 创建ContentProviderOperation.Builder对象来构建更新该id的操作并将其添加到operationList中
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
// 更新Parent_Id字段为目标文件夹id
builder.withValue(NoteColumns.PARENT_ID, folderId);
// 更新Local_Modified字段为1
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
}
try {
// 通过ContentResolver的applyBatch()方法将所有操作提交给ContentProvider执行
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "move notes failed, ids:" + ids.toString());
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
// 如果成功则返回true
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
// 失败则返回false
return false;
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
/**
*
*
* @param resolver ContentResolver
* @return int
*/
public static int getUserFolderCount(ContentResolver resolver) {
// 查询ContentProvider中的所有符合条件的数据并返回查询结果Cursor对象
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
@ -179,30 +125,18 @@ public class DataUtils {
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
// 从Cursor对象中获取查询结果中count字段的值
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
// 关闭游标
cursor.close();
}
}
}
// 返回用户自定义文件夹数量
return count;
}
/**
* idContentProvider
*
* @param resolver ContentResolver
* @param noteId id
* @param type 0
* @return booleanidContentProvider
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
// 查询ContentProvider中指定id的笔记是否存在并返回查询结果Cursor对象
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
@ -214,23 +148,12 @@ public class DataUtils {
if (cursor.getCount() > 0) {
exist = true;
}
// 关闭游标
cursor.close();
}
// 返回指定id的笔记是否存在于ContentProvider中
return exist;
}
/**
* idContentProvider
*
* @param resolver ContentResolver
* @param noteId id
* @return booleanidContentProvider
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
// 查询ContentProvider中指定id的笔记是否存在并返回查询结果Cursor对象
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
@ -239,22 +162,12 @@ public class DataUtils {
if (cursor.getCount() > 0) {
exist = true;
}
// 关闭游标
cursor.close();
}
// 返回指定id的笔记是否存在于ContentProvider中
return exist;
}
/**
* idContentProvider
*
* @param resolver ContentResolver
* @param dataId id
* @return booleanidContentProvider
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
// 查询ContentProvider中指定id的数据是否存在并返回查询结果Cursor对象
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
@ -263,48 +176,28 @@ public class DataUtils {
if (cursor.getCount() > 0) {
exist = true;
}
// 关闭游标
cursor.close();
}
// 返回指定id的数据是否存在于ContentProvider中
return exist;
}
/**
* ContentProvider
*
* @param resolver ContentResolver
* @param name
* @return booleanContentProvider
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
// 查询ContentProvider中指定名称的文件夹是否存在并返回查询结果Cursor对象
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 + "=?",
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) {
exist = true;
}
// 关闭游标
cursor.close();
}
// 返回指定名称的文件夹是否存在于ContentProvider中
return exist;
}
/**
* Widget
*
* @param resolver ContentResolver
* @param folderId id
* @return HashSet<AppWidgetAttribute>Widget
*/
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
// 查询ContentProvider中指定文件夹下的所有Widget信息并返回查询结果Cursor对象
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
@ -317,7 +210,6 @@ public class DataUtils {
set = new HashSet<AppWidgetAttribute>();
do {
try {
// 从Cursor对象中获取查询结果中Widget的id和类型并将其封装到AppWidgetAttribute对象中
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
@ -327,23 +219,12 @@ public class DataUtils {
}
} while (c.moveToNext());
}
// 关闭游标
c.close();
}
// 返回指定文件夹下的所有Widget信息
return set;
}
/**
* id
*
* @param resolver ContentResolver
* @param noteId id
* @return String
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
// 查询ContentProvider中指定笔记id对应的电话号码并返回查询结果Cursor对象
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
@ -356,28 +237,17 @@ public class DataUtils {
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
// 关闭游标
cursor.close();
}
}
// 如果查询失败则返回空字符串
return "";
}
/**
* id
*
* @param resolver ContentResolver
* @param phoneNumber
* @param callDate
* @return longid
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
// 查询ContentProvider中指定电话号码和通话日期对应的笔记id并返回查询结果Cursor对象
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
+ CallNote.PHONE_NUMBER + ",?)",
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null);
@ -389,23 +259,12 @@ public class DataUtils {
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
// 关闭游标
cursor.close();
}
// 如果查询失败则返回0
return 0;
}
/**
* id
*
* @param resolver ContentResolver
* @param noteId id
* @return String
*/
public static String getSnippetById(ContentResolver resolver, long noteId) {
// 查询ContentProvider中指定笔记id对应的摘要信息并返回查询结果Cursor对象
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?",
@ -417,29 +276,20 @@ public class DataUtils {
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
// 关闭游标
cursor.close();
return snippet;
}
// 如果查询失败则抛出异常
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
/**
*
*
* @param snippet
* @return String
*/
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;
}
}

@ -0,0 +1,181 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
public static final int BG_DEFAULT_COLOR = YELLOW;
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
public static class NoteBgResources {
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
return BG_DEFAULT_COLOR;
}
}
public static class NoteItemBgResources {
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
private final static int [] BG_NORMAL_RESOURCES = new int [] {
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
private final static int [] BG_LAST_RESOURCES = new int [] {
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
public static class WidgetBgResources {
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
public static class TextAppearanceResources {
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

@ -41,24 +41,9 @@ import java.io.IOException;
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 mPlayer;
@Override
@ -66,11 +51,9 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 将 Activity 显示在锁屏界面上
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
@ -78,23 +61,19 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 获取 intent 中传递的笔记 ID并根据 ID 获取笔记摘录
Intent intent = getIntent();
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
// 将笔记摘录截取到预设的最大长度,并添加省略号
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
} catch (IllegalArgumentException e) {
// 如果 intent 中未传递有效的笔记 ID则打印异常并退出 Activity
e.printStackTrace();
return;
}
// 创建媒体播放器,并在数据库中找到当前笔记,如果存在则显示提醒对话框并播放提醒音
mPlayer = new MediaPlayer();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
@ -104,82 +83,56 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
}
/**
*
*
* @return true false
*/
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
/**
*
*/
private void playAlarmSound() {
// 获取默认的闹钟铃声 Uri
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 获取静音模式下允许播放的音频流类型
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
// 根据静音模式设置媒体播放器的音频流类型
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
// 设置媒体播放器的数据源,并开始播放
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
*
*/
private void showActionDialog() {
// 构建对话框,并设置标题、内容和两个按钮(确认和进入)
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
// 如果屏幕点亮,则添加“进入”按钮
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
// 显示对话框,并设置对话框关闭时的监听器
dialog.show().setOnDismissListener(this);
}
/**
*
*
* @param dialog
* @param which
*/
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);
@ -190,28 +143,16 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
}
/**
*
*
* @param dialog
*/
public void onDismiss(DialogInterface dialog) {
// 停止播放提醒音并关闭 Activity
stopAlarmSound();
finish();
}
/**
*
*/
private void stopAlarmSound() {
// 如果媒体播放器不为 null则停止播放并释放资源
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}

@ -30,58 +30,36 @@ import net.micode.notes.data.Notes.NoteColumns;
public class AlarmInitReceiver extends BroadcastReceiver {
/**
*
*/
private static final String[] PROJECTION = new String[]{
NoteColumns.ID,
NoteColumns.ALERTED_DATE
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
/**
* ID
*/
private static final int COLUMN_ID = 0;
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
/**
*
*/
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 {
// 获取提醒时间和笔记 ID并构建相应的 Intent 和 PendingIntent
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
Intent sender = new Intent(context, AlarmReceiver.class);
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 设置提醒闹钟
AlarmManager alermManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
c.close();
}
}
}

@ -14,7 +14,6 @@
* limitations under the License.
*/
package net.micode.notes.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@ -38,7 +37,7 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};//小米便签的桌面挂件
};
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
@ -105,7 +104,7 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
*
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;
if (privacyMode) {

@ -14,7 +14,6 @@
* limitations under the License.
*/
package net.micode.notes.widget;
import android.appwidget.AppWidgetManager;
@ -26,7 +25,6 @@ import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
//2倍大小的桌面挂件
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);

@ -14,7 +14,6 @@
* limitations under the License.
*/
package net.micode.notes.widget;
import android.appwidget.AppWidgetManager;
@ -26,7 +25,6 @@ import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
//4倍大小的桌面挂件
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);

Binary file not shown.

@ -1,929 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName();
public static final int STATE_SUCCESS = 0;
public static final int STATE_NETWORK_ERROR = 1;
public static final int STATE_INTERNAL_ERROR = 2;
public static final int STATE_SYNC_IN_PROGRESS = 3;
public static final int STATE_SYNC_CANCELLED = 4;
private static GTaskManager mInstance = null;
private Activity mActivity;
private Context mContext;
private ContentResolver mContentResolver;
private boolean mSyncing;
private boolean mCancelled;
private HashMap<String, TaskList> mGTaskListHashMap;
private HashMap<String, Node> mGTaskHashMap;
private HashMap<String, MetaData> mMetaHashMap;
private TaskList mMetaList;
private HashSet<Long> mLocalDeleteIdMap;
private HashMap<String, Long> mGidToNid;
private HashMap<Long, String> mNidToGid;
// 定义了一个名为 GTaskManager 的私有类
private GTaskManager() {
// 初始化一些变量
mSyncing = false; // 当前不在同步状态
mCancelled = false; // 当前没有取消
mGTaskListHashMap = new HashMap<String, TaskList>(); // 存储多个任务列表的 HashMap
mGTaskHashMap = new HashMap<String, Node>(); // 存储任务的 HashMap
mMetaHashMap = new HashMap<String, MetaData>(); // 存储元数据的 HashMap
mMetaList = null; // 初始化元数据列表为空
mLocalDeleteIdMap = new HashSet<Long>(); // 存储本地删除的任务的 ID 集合
mGidToNid = new HashMap<String, Long>(); // 存储 GTask ID 到 Node ID 的映射
mNidToGid = new HashMap<Long, String>(); // 存储 Node ID 到 GTask ID 的映射
}
// 定义了一个名为 getInstance 的静态方法,返回一个 GTaskManager 对象
public static synchronized GTaskManager getInstance() {
// 如果当前 GTaskManager 对象为空,就创建一个新的 GTaskManager 对象
if (mInstance == null) {
mInstance = new GTaskManager();
}
// 返回 GTaskManager 对象
return mInstance;
}
// 定义了一个名为 setActivityContext 的方法,用于设置 Activity 的上下文
public synchronized void setActivityContext(Activity activity) {
// 用于获取授权令牌authtoken的 Activity 上下文
mActivity = activity;
}
// 定义了一个名为 sync 的方法,用于同步 Google 任务列表
public int sync(Context context, GTaskASyncTask asyncTask) {
// 如果正在同步,则返回“同步进行中”状态
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
// 重置相关变量
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
// 获取 GTaskClient 的单例对象
GTaskClient client = GTaskClient.getInstance();
// 重置更新数组
client.resetUpdateArray();
// 登录 Google 任务列表
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// 从 Google 获取任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 进行内容同步工作
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
// 清空相关变量
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
// 如果已取消,则返回“同步已取消”状态;否则返回“成功”状态
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
// 初始化 GTask 列表
private void initGTaskList() throws NetworkFailureException {
// 检查是否已取消
if (mCancelled)
return;
// 获取 GTask 客户端
GTaskClient client = GTaskClient.getInstance();
try {
// 获取任务列表的 JSON 数据
JSONArray jsTaskLists = client.getTaskLists();
// 先初始化元列表
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
// 获取 JSON 数据中的任务列表对象
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 如果任务列表名称为“MIUI_FOLDER_PREFFIX + FOLDER_META”则表示为元列表
if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
// 初始化元列表
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
metaData.setContentByRemoteJSON(object);
// 如果元数据值得保存,将其添加到元列表中
if (metaData.isWorthSaving()) {
mMetaList.addChildTask(metaData);
// 将元数据关联的 GID 和元数据存储在哈希映射表中
if (metaData.getGid() != null) {
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// 如果元列表不存在,则创建元列表
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
// 获取 JSON 数据中的任务列表对象
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 如果任务列表名称以“MIUI_FOLDER_PREFFIX”开头且不是元列表则为任务列表
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) {
// 初始化任务列表
TaskList tasklist = new TaskList();
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// 加载任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
// 如果任务值得保存,将其添加
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid));// 设置任务元信息
tasklist.addChildTask(task);// 将任务添加到任务列表的子任务中
mGTaskHashMap.put(gid, task); // 在哈希表中添加任务的ID和任务对象
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
// 同步笔记内容
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
mLocalDeleteIdMap.clear();
if (mCancelled) {// 如果已取消,直接返回
return;
}
// 处理本地已删除笔记
try {
// 查询非系统类型且父级 ID 不是回收站的笔记
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 先同步文件夹
syncFolder();
// 处理已存在于数据库中的笔记
try {
// 查询类型为笔记且父级 ID 不是回收站的笔记,按类型倒序排序
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);// 获取同步类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地新增
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing note in database");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理剩余项
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {// 如果还有下一个键值对
Map.Entry<String, Node> entry = iter.next();// 获取下一个键值对
node = entry.getValue();// 获取键值对中的value并将其赋值给node
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);// 将node添加到本地同步
}
// mCancelled 可能会被另一个线程设置,因此我们需要逐个检查
// 清除本地删除表
if (!mCancelled) {// 如果未被取消
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {// 批量删除本地删除表中的笔记
throw new ActionFailureException("failed to batch-delete local deleted notes");
}// 如果删除失败则抛出异常
}
// 刷新本地同步id
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
private void syncFolder() throws NetworkFailureException {
Cursor c = null;// 游标对象,用于访问数据
String gid;// GTask任务列表的ID
Node node;// GTask任务列表对应的本地节点
int syncType;// 本地与远程数据同步的类型
if (mCancelled) {// 如果同步已被取消,则直接返回
return;
}
// for root folder
try {
// 查询根目录的数据
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null) {
c.moveToNext();// 将游标移动到下一个记录
gid = c.getString(SqlNote.GTASK_ID_COLUMN);// 获取GTask任务列表ID
node = mGTaskHashMap.get(gid);// 从GTask任务列表映射中获取对应本地节点
if (node != null) {
mGTaskHashMap.remove(gid);// 从映射中移除该节点
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);// 将GTask ID映射到本地ID
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);// 将本地ID映射到GTask ID
// 仅当名称不是默认值时,才更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);// 同步本地和远程数据
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);// 将本地数据同步到远程
}
} else {
Log.w(TAG, "failed to query root folder");// 记录警告信息
}
} finally {
if (c != null) {
c.close();// 关闭游标
c = null;// 将游标对象设置为null
}
}
// for call-note folder
try {
// 查询通话记录文件夹的数据
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);// 获取GTask任务列表ID
node = mGTaskHashMap.get(gid);// 从GTask任务列表映射中获取对应本地节点
if (node != null) {
mGTaskHashMap.remove(gid);// 从映射中移除该节点
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);// 将GTask ID映射到本地ID
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);// 将本地ID映射到GTask ID
// 仅当名称不是默认值时,才更新远端
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
}
} else {
Log.w(TAG, "failed to query call note folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理本地现有文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地新增
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远端删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理远端新增文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
// 如果取消了同步,则直接返回
if (mCancelled) {
return;
}
// 元数据对象
MetaData meta;
// 根据同步类型执行相应的操作
switch (syncType) {
// 本地添加节点
case Node.SYNC_ACTION_ADD_LOCAL:
addLocalNode(node);
break;
// 远程添加节点
case Node.SYNC_ACTION_ADD_REMOTE:
addRemoteNode(node, c);
break;
// 本地删除节点
case Node.SYNC_ACTION_DEL_LOCAL:
// 获取需要删除的节点的元数据
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) {
// 使用 GTaskClient 删除节点
GTaskClient.getInstance().deleteNode(meta);
}
// 将节点 ID 添加到本地删除 ID 映射表中
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
// 远程删除节点
case Node.SYNC_ACTION_DEL_REMOTE:
// 获取需要删除的节点的元数据
meta = mMetaHashMap.get(node.getGid());
if (meta != null) {
// 使用 GTaskClient 删除节点
GTaskClient.getInstance().deleteNode(meta);
}
// 使用 GTaskClient 删除节点
GTaskClient.getInstance().deleteNode(node);
break;
// 本地更新节点
case Node.SYNC_ACTION_UPDATE_LOCAL:
updateLocalNode(node, c);
break;
// 远程更新节点
case Node.SYNC_ACTION_UPDATE_REMOTE:
updateRemoteNode(node, c);
break;
// 更新冲突
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// 可以考虑合并两个修改
// 现在先简单地使用本地更新
updateRemoteNode(node, c);
break;
// 不执行任何操作
case Node.SYNC_ACTION_NONE:
break;
// 同步操作失败
case Node.SYNC_ACTION_ERROR:
default:
// 抛出异常
throw new ActionFailureException("unkown sync action type");
}
}
private void addLocalNode(Node node) throws NetworkFailureException {
// 如果取消了操作,直接返回
if (mCancelled) {
return;
}
// 声明一个 SqlNote 对象
SqlNote sqlNote;
// 如果是 TaskList判断是哪种文件夹
if (node instanceof TaskList) {
// 如果是默认文件夹
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
// 新建一个根文件夹的 SqlNote 对象
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
}
// 如果是通话记录文件夹
else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
// 新建一个通话记录文件夹的 SqlNote 对象
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
}
// 如果是其他类型的文件夹
else {
// 新建一个普通的 SqlNote 对象,并设置其内容和父文件夹 ID
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
}
// 如果是 Task
else {
// 新建一个普通的 SqlNote 对象
sqlNote = new SqlNote(mContext);
// 从 Task 中获取其内容的 JSON 对象
JSONObject js = node.getLocalJSONFromContent();
try {
// 如果 JSON 对象中包含元数据头部信息
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
// 获取元数据头部信息中的 Note 对象
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 如果 Note 对象中包含 ID
if (note.has(NoteColumns.ID)) {
// 获取 Note 的 ID
long id = note.getLong(NoteColumns.ID);
// 如果该 ID 在 Note 数据库中已经存在
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// 该 ID 已经被占用,需要新建一个 ID
note.remove(NoteColumns.ID);
}
}
}
// 如果 JSON 对象中包含元数据数据部分
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
// 获取元数据数据部分的 JSON 数组
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 遍历该 JSON 数组
for (int i = 0; i < dataArray.length(); i++) {
// 获取数组中的 JSON 对象
JSONObject data = dataArray.getJSONObject(i);
// 如果该 JSON 对象中包含 ID
if (data.has(DataColumns.ID)) {
// 获取该数据的 ID
long dataId = data.getLong(DataColumns.ID);
// 如果该 ID 在数据数据库中已经存在
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// 该 ID 已经被占用,需要新建一个 ID
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {// 捕获 JSON 解析异常
Log.w(TAG, e.toString());// 记录警告日志
e.printStackTrace();// 打印异常堆栈信息
}
sqlNote.setContent(js);// 设置 SQLNote 的内容为解析出的 js
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());// 获取 Task 节点的父节点的 GID 对应的 NID
// 如果获取到的 NID 为空
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");// 记录错误日志
throw new ActionFailureException("cannot add local node");// 抛出 ActionFailureException 异常
}
sqlNote.setParentId(parentId.longValue());// 将 SQLNote 的父节点 ID 设置为获取到的 NID
}
// 创建本地节点
sqlNote.setGtaskId(node.getGid());// 设置 SQLNote 的 GTask ID 为节点的 GID
sqlNote.commit(false);// 将 SQLNote 提交到本地数据库,不需要立即同步到远程服务器
// 更新 GID-NID 映射
mGidToNid.put(node.getGid(), sqlNote.getId());// 将节点的 GID 映射为 SQLNote 的 ID
mNidToGid.put(sqlNote.getId(), node.getGid());// 将 SQLNote 的 ID 映射为节点的 GID
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote);// 同步 SQLNote 的元数据到远程服务器
}
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
// 如果操作已经被取消,则不进行任何操作
if (mCancelled) {
return;
}
SqlNote sqlNote;
// 将数据库查询结果保存到 SqlNote 对象中
sqlNote = new SqlNote(mContext, c);
// 将节点内容转换为 JSON 格式并保存到 SqlNote 对象中
sqlNote.setContent(node.getLocalJSONFromContent());
// 如果节点是任务类型,则将其父节点的 ID 设置为对应的本地 ID
// 否则将其父节点的 ID 设置为根文件夹的 ID
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
// 如果 parentId 为 null则说明无法在本地找到该任务的父节点 ID
// 此时抛出一个异常
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
// 将父节点 ID 设置为 sqlNote 对象的 parentId 属性
sqlNote.setParentId(parentId.longValue());
// 将 sqlNote 对象的属性保存到数据库中
sqlNote.commit(true);
// 更新远程节点的元信息
updateRemoteMeta(node.getGid(), sqlNote);
}
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
// 如果任务已经被取消,直接返回
if (mCancelled) {
return;
}
// 通过Cursor获取SqlNote对象
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// 如果SqlNote是任务类型进行以下操作
if (sqlNote.isNoteType()) {
// 创建一个Task对象并用SqlNote的内容更新它
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
// 获取Task对象的父任务列表的gid
String parentGid = mNidToGid.get(sqlNote.getParentId());
// 如果无法找到父任务列表的gid抛出ActionFailureException异常
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task");
}
// 在父任务列表中添加子任务
mGTaskListHashMap.get(parentGid).addChildTask(task);
// 通过GTaskClient实例创建Task任务
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// 添加元数据
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// 如果SqlNote是文件夹类型进行以下操作
// 如果是根文件夹,设置文件夹名为默认文件夹
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT;
// 如果是通话记录文件夹,设置文件夹名为通话记录文件夹
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
// 否则将文件夹名设置为SqlNote对象的片段内容
else
folderName += sqlNote.getSnippet();
// 遍历mGTaskListHashMap查找是否存在与SqlNote对象匹配的TaskList对象
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
// 如果存在匹配的TaskList对象则将tasklist设置为该对象
if (list.getName().equals(folderName)) {
tasklist = list;
// 如果mGTaskHashMap包含gid将其从中移除
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// 如果未找到匹配的TaskList对象新建一个TaskList对象
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
}
// 更新SqlNote对象的GTaskId并将其提交到本地数据库
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id 映射表
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return; // 如果任务已取消,则退出此方法
}
SqlNote sqlNote = new SqlNote(mContext, c); // 创建 SqlNote 实例
// 更新远程服务器的节点内容
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
// 更新节点的元数据
updateRemoteMeta(node.getGid(), sqlNote);
// 如果是笔记类型,则移动任务(子任务)到另一个任务列表中
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent(); // 获取任务当前的父任务列表
// 查找新的父任务列表的 GID
String curParentGid = mNidToGid.get(sqlNote.getParentId());
if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist"); // 找不到新父任务列表,记录错误并抛出异常
throw new ActionFailureException("cannot update remote task");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 获取新父任务列表的 TaskList 实例
// 如果新旧父任务列表不同,则移动任务到新的父任务列表
if (preParentList != curParentList) {
preParentList.removeChildTask(task); // 从旧父任务列表中移除该任务
curParentList.addChildTask(task); // 添加该任务到新的父任务列表中
GTaskClient.getInstance().moveTask(task, preParentList, curParentList); // 移动任务到新父任务列表
}
}
sqlNote.resetLocalModified(); // 重置本地修改标记
sqlNote.commit(true); // 将 SqlNote 中的修改提交到本地 SQLite 数据库中
}
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
// 如果 sqlNote 不为空且为笔记类型,则执行以下代码
if (sqlNote != null && sqlNote.isNoteType()) {
// 获取 gid 对应的 metaData
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
// 设置 gid 对应的 metaData 的内容为 sqlNote 的内容
metaData.setMeta(gid, sqlNote.getContent());
// 将 metaData 添加到更新队列中
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
// 如果 metaData 为空,则创建新的 metaData并设置其内容为 sqlNote 的内容
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
// 将 metaData 添加到 mMetaList 的子任务列表中
mMetaList.addChildTask(metaData);
// 将 metaData 添加到 mMetaHashMap 中
mMetaHashMap.put(gid, metaData);
// 创建任务并将其添加到 GTaskClient 的任务列表中
GTaskClient.getInstance().createTask(metaData);
}
}
}
// 声明一个方法,用于更新本地同步 ID如果网络故障则抛出 NetworkFailureException 异常
private void refreshLocalSyncId() throws NetworkFailureException {
// 如果已取消,则直接返回
if (mCancelled) {
return;
}
// 清空哈希表
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
// 初始化 GTask 列表
initGTaskList();
// 声明一个游标,用于查询本地便签
Cursor c = null;
try {
// 查询不属于系统类型和垃圾桶类型的所有便签
c = mContentResolver.query(
Notes.CONTENT_NOTE_URI,
SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)",
new String[] {
String.valueOf(Notes.TYPE_SYSTEM),
String.valueOf(Notes.ID_TRASH_FOLER)
},
NoteColumns.TYPE + " DESC"
);
// 如果查询结果不为空
if (c != null) {
// 遍历查询结果
while (c.moveToNext()) {
// 获取当前便签的 GTask ID
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 根据 GTask ID 从 GTask 哈希表中获取节点信息
Node node = mGTaskHashMap.get(gid);
// 如果节点信息不为空
if (node != null) {
// 从 GTask 哈希表中移除当前 GTask ID 对应的节点
mGTaskHashMap.remove(gid);
// 创建一个 ContentValues 对象,用于更新本地便签的同步 ID
ContentValues values = new ContentValues();
values.put(NoteColumns.SYNC_ID, node.getLastModified());
// 更新本地便签的同步 ID
mContentResolver.update(
ContentUris.withAppendedId(
Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)
),
values,
null,
null
);
} else {
// 如果节点信息为空,打印日志并抛出 ActionFailureException 异常
Log.e(TAG, "something is missed");
throw new ActionFailureException(
"some local items don't have gid after sync"
);
}
}
} else {
// 如果查询结果为空,打印警告日志
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
// 关闭游标
if (c != null) {
c.close();
c = null;
}
}
}
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
mCancelled = true;
}
}

@ -1,39 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class);
//启动AlarmAlertActivity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//activity要存在于activity的栈中而非activity的途径启动activity时必然不存在一个activity的栈
//所以要新起一个栈装入启动的activity
context.startActivity(intent);
}
}
//这是实现alarm这个功能最接近用户层的包基于上面的两个包
<<<<<<< remotes/origin/lzj_branch
//作用还需要深究但是对于setClass和addFlags的
=======
//作用还需要深究但是对于setClass和addFlags的
>>>>>>> local

@ -1,495 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class DateTimePicker extends FrameLayout {
//FrameLayout是布局模板之一
//所有的子元素全部在屏幕的右上方
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
//初始化控件
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
//NumberPicker是数字选择器
//这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午)
private Calendar mDate;
//定义了Calendar类型的变量mDate用于操作时间
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
}
};//OnValueChangeListener这是时间改变监听器这里主要是对日期的监听
//将现在日期的值传递给mDateupdateDateControl是同步操作
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
//这里是对 小时Hour 的监听
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
//声明一个Calendar的变量cal便于后续的操作
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
//这里是对于12小时制时晚上11点和12点交替时对日期的更改
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
//这里是对于12小时制时凌晨11点和12点交替时对日期的更改
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
//这里是对于12小时制时中午11点和12点交替时对AM和PM的更改
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
//这里是对于24小时制时晚上11点和12点交替时对日期的更改
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}//这里是对于12小时制时凌晨11点和12点交替时对日期的更改
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
//通过数字选择器对newHour的赋值
mDate.set(Calendar.HOUR_OF_DAY, newHour);
//通过set函数将新的Hour值传给mDate
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
//这里是对 分钟Minute改变的监听
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
//设置offset作为小时改变的一个记录数据
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
//如果原值为59新值为0则offset加1
//如果原值为0新值为59则offset减1
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
//对AM和PM的监听
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
//通过对数据库的访问,获取当前的系统时间
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}//上面函数的得到的是一个天文数字1970至今的秒数需要DateFormat将其变得有意义
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);//获取系统时间
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);
//如果当前Activity里用到别的layout比如对话框layout
//还要设置这个layout上的其他组件的内容就必须用inflate()方法先将对话框的layout找出来
//然后再用findViewById()找到它上面的其它组件
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
}
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
}
//存在疑问setEnabled的作用
//下面的代码通过原程序的注释已经比较清晰,另外可以通过函数名来判断
//下面的各函数主要是对上面代码引用到的各函数功能的实现
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
//实现函数——得到当前的秒数
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}//实现函数功能——设置当前的时间参数是date
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}//实现函数功能——设置当前的时间,参数是各详细的变量
/**
* Get current year
*
* @return The current year
*/
//下面是得到year、month、day等值
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}// 对于星期几的算法
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}// 对于上下午操作的算法
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}// 对于小时的算法
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -1,102 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
//创建一个Calendar类型的变量 mDate方便时间的操作
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
//声明一个时间日期滚动选择控件 mOnDateTimeSetListener
private DateTimePicker mDateTimePicker;
//DateTimePicker控件控件一般用于让用户可以从日期列表中选择单个值。
//运行时,单击控件边上的下拉箭头,会显示为两个部分:一个下拉列表,一个用于选择日期的
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
//对该界面对话框的实例化
super(context);
//对数据库的操作
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
//添加一个子视图
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
//将视图中的各选项设置为系统当前时间
updateTitle(mDate.getTimeInMillis());
}
});
mDate.setTimeInMillis(date);
//得到系统时间
mDate.set(Calendar.SECOND, 0);
//将秒数设置为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
//设置按钮
set24HourView(DateFormat.is24HourFormat(this.getContext()));
//时间标准化打印
updateTitle(mDate.getTimeInMillis());
}
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}//将时间日期滚动选择控件实例化
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}//android开发中常见日期管理工具类API——DateUtils按照上下午显示时间
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}//第一个参数arg0是接收到点击事件的对话框
//第二个参数arg1是该对话框上的按钮
}

@ -1,67 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
//声明一个下拉菜单
private Menu mMenu;
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
//设置这个view的背景
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
//MenuInflater是用来实例化Menu目录下的Menu布局文件
//根据ID来确认menu的内容选项
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}//设置菜单的监听
}
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
//对于菜单选项的初始化,根据索引搜索菜单需要的选项
public void setTitle(CharSequence title) {
mButton.setText(title);
}
//布局文件,设置标题
}

@ -1,87 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
//CursorAdapter是Cursor和ListView的接口
//FoldersListAdapter继承了CursorAdapter的类
//主要作用是便签数据库和用户的交互
//这里就是用folder文件夹的形式展现给用户
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};//调用数据库中便签的ID和片段
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}//数据库操作
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
//ViewGroup是容器
return new FolderListItem(context);
}//创建一个文件夹,对于各文件夹中子标签的初始化
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}//将各个布局文件绑定起来
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}//根据数据库中标签的ID得到标签的各项内容
private class FolderListItem extends LinearLayout {
private TextView mName;
public FolderListItem(Context context) {
super(context);
//操作数据库
inflate(context, R.layout.folder_list_item, this);
//根据布局文件的名字等信息将其找出来
mName = (TextView) findViewById(R.id.tv_folder_name);
}
public void bind(String name) {
mName.setText(name);
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,272 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
//继承edittext设置便签设置文本框
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
//建立一个字符和整数的hash表用于链接电话网站还有邮箱
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
//在NoteEditActivity中删除或添加文本的操作可以看做是一个文本是否被变的标记英文注释已说明的很清楚
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;
//根据context设置文本
public NoteEditText(Context context) {
super(context, null);//用super引用父类变量
mIndex = 0;
}
//设置当前光标
public void setIndex(int index) {
mIndex = index;
}
//初始化文本修改标记
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
//AttributeSet 百度了一下是自定义空控件属性,用于维护便签动态变化的属性
//初始化便签
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// 根据defstyle自动初始化
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
//view里的函数处理手机屏幕的所有事件
/*event
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//重写了需要处理屏幕被按下的事件
case MotionEvent.ACTION_DOWN:
//跟新当前坐标值
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
//用布局控件layout根据x,y的新值设置新的位置
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
//更新光标新的位置
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event);
}
/*
*
*
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
//根据按键的 Unicode 编码值来处理
case KeyEvent.KEYCODE_ENTER:
//“进入”按键
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
//“删除”按键
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
//继续执行父类的其他点击事件
return super.onKeyDown(keyCode, event);
}
@Override
/*
*
*
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
//根据按键的 Unicode 编码值来处理有删除和进入2种操作
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
//若是被修改过
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
//若之前有被修改并且文档不为空
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
//利用上文OnTextViewChangeListener对KEYCODE_DEL按键情况的删除函数进行删除
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
//其他情况报错,文档的改动监听器并没有建立
}
break;
case KeyEvent.KEYCODE_ENTER:
//同上也是分为监听器是否建立2种情况
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
//获取当前位置
String text = getText().subSequence(selectionStart, length()).toString();
//获取当前文本
setText(getText().subSequence(0, selectionStart));
//根据获取的文本设置当前文本
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
//当{@link KeyEvent#KEYCODE_ENTER}添加新文本
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
//其他情况报错,文档的改动监听器并没有建立
}
break;
default:
break;
}
//继续执行父类的其他按键弹起的事件
return super.onKeyUp(keyCode, event);
}
@Override
/*
*
*
* focusedViewFocusedtruefalse
direction
RectViewnull
*/
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
//若监听器已经建立
if (!focused && TextUtils.isEmpty(getText())) {
//获取到焦点并且文本不为空
mOnTextViewChangeListener.onTextChange(mIndex, false);
//mOnTextViewChangeListener子函数置false隐藏事件选项
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
//mOnTextViewChangeListener子函数置true显示事件选项
}
}
//继续执行父类的其他焦点变化的事件
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
@Override
/*
*
*
*/
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
//有文本存在
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
//获取文本开始和结尾位置
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
//获取开始到结尾的最大值和最小值
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
//设置url的信息的范围值
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
//获取计划表中所有的key值
if(urls[0].getURL().indexOf(schema) >= 0) {
//若url可以添加则在添加后将defaultResId置为key所映射的值
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
//defaultResId == 0则说明url并没有添加任何东西所以置为连接其他SchemaActionResMap的值
defaultResId = R.string.note_link_other;
}
//建立菜单
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
//新建按键监听器
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
//根据相应的文本设置菜单的按键
return true;
}
});
}
}
//继续执行父类的其他菜单创建的事件
super.onCreateContextMenu(menu);
}
}

@ -1,232 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
};
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
//初始化NoteItemData主要利用光标cursor获取的东西
public NoteItemData(Context context, Cursor cursor) {
//getxxx为转换格式
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
//初始化电话号码的信息
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
//mphonenumber里有符合字符串则用contart功能连接
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
if (mName == null) {
mName = "";
}
checkPostion(cursor);
}
//根据鼠标的位置设置标记,和位置
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
//初始化“多重子文件”“单一子文件”2个标记
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
//主要是设置上诉2标记
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
//若是note格式并且不是第一个元素
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
//获取光标位置后看上一行
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
//若光标满足系统或note格式
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
//若是数据行数大于但前位置+1则设置成正确
} else {
mIsOneNoteFollowingFolder = true;
//否则单一文件夹标记为true
}
}
if (!cursor.moveToNext()) {//若不能再往下走则报错
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
//若数据父id为保存至文件夹模式的id且满足电话号码单元不为空则isCallRecord为true
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

File diff suppressed because it is too large Load Diff

@ -1,274 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/*
* 便CursorAdaptercursorListView
* NotesListAdapter便
*/
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; //选择模式标记
/*
* widget
*/
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
};
/*
* 便
*
*/
public NotesListAdapter(Context context) {
super(context, null); //父类对象置空
mSelectedIndex = new HashMap<Integer, Boolean>(); //新建选项下标的hash表
mContext = context;
mNotesCount = 0;
}
@Override
/*
*
* 使NotesListItem
*/
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
/*
*
*
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
//若view是NotesListItem的一个实例
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
//则新建一个项目选项并且用bind跟将view和鼠标内容便签数据捆绑在一起
}
}
/*
*
*
*/
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
//根据定位和是否勾选设置下标
notifyDataSetChanged();
//在修改后刷新activity
}
/*
*
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/*
*
* mode
*/
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
/*
*
*
*/
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
//获取光标位置
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
//遍历所有光标可用的位置在判断为便签类型之后勾选单项框
}
/*
*
*
*/
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
//建立hash表
for (Integer position : mSelectedIndex.keySet()) {
//遍历所有的关键
if (mSelectedIndex.get(position) == true) {
//若光标位置可用
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
//原文件不需要添加
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
//则将id该下标假如选项集合中
}
}
return itemSet;
}
/*
* Widget
*
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
//以上4句和getSelectedItemIds一样不再重复
if (c != null) {
//光标位置可用的话就建立新的Widget属性并编辑下标和类型最后添加到选项集中
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
/*
*
*
*/
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
//首先获取选项下标的值
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
//初始化叠加器
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
//若value值为真计数+1
count++;
}
}
return count;
}
/*
*
*
*/
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
//获取选项数看是否等于便签的个数
}
/*
*
*
*/
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
@Override
/*
* activity便
*
*/
protected void onContentChanged() {
super.onContentChanged();
//执行基类函数
calcNotesCount();
}
@Override
/*
* activity便
*/
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
//执行基类函数
calcNotesCount();
}
/*
* 便
*
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
//获取总数同时遍历
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
//若该位置不为空并且文本类型为便签就+1
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
//否则报错
}
}
}

@ -1,148 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
//创建便签列表项目选项
public class NotesListItem extends LinearLayout {
private ImageView mAlert;//闹钟图片
private TextView mTitle; //标题
private TextView mTime; //时间
private TextView mCallName; //
private NoteItemData mItemData; //标签数据
private CheckBox mCheckBox; //打钩框
/*初始化基本信息*/
public NotesListItem(Context context) {
super(context); //super()它的主要作用是调整调用父类构造函数的顺序
inflate(context, R.layout.note_item, this);//Inflate可用于将一个xml中定义的布局控件找出来,这里的xml是r.layout
//findViewById用于从contentView中查找指定ID的View转换出来的形式根据需要而定
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);
}
//根据data的属性对各个控件的属性的控制主要是可见性Visibility内容setText格式setTextAppearance
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE); ///设置可见行为可见
mCheckBox.setChecked(checked); ///格子打钩
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
///设置控件属性一共三种情况由data的id和父id是否与保存到文件夹的id一致来决定
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
//设置该textview的style
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
//setText为设置内容
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
///给闹钟进行相关设置
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);//图片来源的设置
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
///设置title格式
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);///设置图片来源
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
///设置内容获取相关时间从data里编辑的日期中获取
mTime. setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setBackground(data);
}
//根据data的文件属性来设置背景
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
//若是note型文件则4种情况对于4种不同情况的背景来源
if (data.getType() == Notes.TYPE_NOTE) {
//单个数据并且只有一个子文件夹
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {//是最后一个数据
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {//是一个数据并有多个子文件夹
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
//若不是note直接调用文件夹的背景来源
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
public NoteItemData getItemData() {
return mItemData;
}
}

@ -1,546 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/*
*便
* PreferenceActivityActivity
*/
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;
//账户的hash标记
@Override
/*
*activity
*Bundle icicle activity
*
*/
protected void onCreate(Bundle icicle) {
//先执行父类的创建函数
super.onCreate(icicle);
/* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true);
//给左上角图标的左边加上一个返回的图标
addPreferencesFromResource(R.xml.preferences);
//添加xml来源并显示 xml
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
//根据同步账户关键码来初始化分组
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
//初始化同步组件
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
//获取listView
// ListView的作用:用于列出所有选择
getListView().addHeaderView(header, null, true);
//在listView组件上方添加其他组件
}
@Override
/*
* activity
*
*/
protected void onResume() {
//先执行父类 的交互实现
super.onResume();
// need to set sync account automatically if user has added a new
// account
if (mHasAddedAccount) {
//若用户新加了账户则自动设置同步账户
Account[] accounts = getGoogleAccounts();
//获取google同步账户
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
//若原账户不为空且当前账户有增加
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
//更新账户
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
//若是没有找到旧的账户,那么同步账号中就只添加新账户
break;
}
}
}
}
refreshUI();
//刷新标签界面
}
@Override
/*
* activity
*
*/
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
//注销接收器
}
super.onDestroy();
//执行父类的销毁动作
}
/*
*
*
*/
private void loadAccountPreference() {
mAccountCategory.removeAll();
//销毁所有的分组
Preference accountPref = new Preference(this);
//建立首选项
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
//设置首选项的大标题和小标题
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
//建立监听器
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account
//若是第一次建立账户显示选择账户提示对话框
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
//若是已经建立则显示修改对话框并进行修改操作
showChangeAccountConfirmAlertDialog();
}
} else {
//若在没有同步的情况下则在toast中显示不能修改
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
//根据新建首选项编辑新的账户分组
mAccountCategory.addPreference(accountPref);
}
/*
*
*
*/
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
//获取同步按钮控件和最终同步时间的的窗口
// set button state
//设置按钮的状态
if (GTaskSyncService.isSyncing()) {
//若是在同步状态下
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
//设置按钮显示的文本为“取消同步”以及监听器
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
//若是不同步则设置按钮显示的文本为“立即同步”以及对应监听器
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
//设置按键可用还是不可用
// set last sync time
// 设置最终同步时间
if (GTaskSyncService.isSyncing()) {
//若是在同步的情况下
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
// 根据当前同步服务器设置时间显示框的文本以及可见性
} else {
//若是非同步情况
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
//则根据最后同步时间的信息来编辑时间显示框的文本内容和可见性
} else {
//若时间为空直接设置为不可见状态
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
/*
*
*
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/*
*
*
*/
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
//创建一个新的对话框
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
//设置标题以及子标题的内容
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
//设置对话框的自定义标题建立一个YES的按钮
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
//获取同步账户信息
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
//若账户不为空
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
//在账户列表中查询到所需账户
}
items[index++] = account.name;
}
dialogBuilder.setSingleChoiceItems(items, checkedItem,
//在对话框建立一个单选的复选框
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
//取消对话框
refreshUI();
}
//设置点击后执行的事件,包括检录新同步账户和刷新标签界面
});
//建立对话框网络版的监听器
}
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
//给新加账户对话框设置自定义样式
final AlertDialog dialog = dialogBuilder.show();
//显示对话框
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
//将新加账户的hash置true
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
//建立网络建立组件
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
startActivityForResult(intent, -1);
//跳回上一个选项
dialog.dismiss();
}
});
//建立新加账户对话框的监听器
}
/*
*
*
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
//创建一个新的对话框
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
//根据同步修改的账户信息设置标题以及子标题的内容
dialogBuilder.setCustomTitle(titleView);
//设置对话框的自定义标题
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
//定义一些标记字符串
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
//设置对话框要显示的一个list用于显示几个命令时,即changeremovecancel
public void onClick(DialogInterface dialog, int which) {
//按键功能由which来决定
if (which == 0) {
//进入账户选择对话框
showSelectAccountAlertDialog();
} else if (which == 1) {
//删除账户并且跟新便签界面
removeSyncAccount();
refreshUI();
}
}
});
dialogBuilder.show();
//显示对话框
}
/*
*
*
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/*
*
*
*/
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
//假如该账号不在同步账号列表中
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
//编辑共享的首选项
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
//将该账号加入到首选项中
editor.commit();
//提交修改的数据
setLastSyncTime(this, 0);
//将最后同步时间清零
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
//重置当地同步任务的信息
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
//将toast的文本信息置为“设置账户成功”并显示出来
}
}
/*
*
*
*/
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
//设置共享首选项
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
//假如当前首选项中有账户就删除
}
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
//删除当前首选项中有账户时间
}
editor.commit();
//提交更新后的数据
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
//重置当地同步任务的信息
}
/*
*
*
*/
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/*
*
*
*/
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 从共享首选项中找到相关账户并获取其编辑器
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit();
//编辑最终同步时间并提交更新
}
/*
*
*
*/
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
/*
*
* BroadcastReceiver
*/
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
refreshUI();
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
//获取随广播而来的Intent中的同步服务的数据
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
//通过获取的数据在设置系统的状态
}
}
}
/*
*
*
* :MenuItem
*/
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
//根据选项的id选择这里只有一个主页
case android.R.id.home:
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
//在主页情况下在创建连接组件intent发出清空的信号并开始一个相应的activity
default:
return false;
}
}
}

@ -1,97 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
// 导入所需的类和包
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
// MetaData类继承自Task类用于处理与Google任务相关的元数据
public class MetaData extends Task {
// 定义类的TAG用于日志记录
private final static String TAG = MetaData.class.getSimpleName();
// 定义一个变量用于存储与Google任务相关的ID
private String mRelatedGid = null;
// 设置元数据方法接收一个gid和一个metaInfo JSONObject对象
public void setMeta(String gid, JSONObject metaInfo) {
try {
// 将gid存储在metaInfo的JSON对象中
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
// 记录错误日志
Log.e(TAG, "failed to put related gid");
}
// 记录错误日志
setNotes(metaInfo.toString());
// 设置元数据任务的名称
setName(GTaskStringUtils.META_NOTE_NAME);
}
// 获取与Google任务相关的ID的方法
public String getRelatedGid() {
return mRelatedGid;
}
// 重写isWorthSaving方法判断是否值得保存
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
// 从远程JSON设置内容
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
// 解析notes中的JSON字符串
JSONObject metaInfo = new JSONObject(getNotes().trim());
// 从JSON对象中获取与Google任务相关的ID
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
// 记录警告日志
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
// 从本地JSON设置内容但是不应该被调用因此抛出异常
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
// 从内容获取本地JSON但是不应该被调用因此抛出异常
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
// 获取同步操作,但是不应该被调用,因此抛出异常
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -1,113 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
// 导入所需的类和包
import android.database.Cursor;
import org.json.JSONObject;
// 定义一个抽象类Node用于表示各种数据节点
public abstract class Node {
// 定义同步操作的常量
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
// 定义节点的基本属性
private String mGid;
private String mName;
private long mLastModified;
private boolean mDeleted;
// 构造方法,初始化节点的属性
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
// 定义抽象方法用于获取创建操作的JSON对象
public abstract JSONObject getCreateAction(int actionId);
// 定义抽象方法用于获取更新操作的JSON对象
public abstract JSONObject getUpdateAction(int actionId);
// 定义抽象方法用于根据远程JSON设置节点的内容
public abstract void setContentByRemoteJSON(JSONObject js);
// 定义抽象方法用于根据本地JSON设置节点的内容
public abstract void setContentByLocalJSON(JSONObject js);
// 定义抽象方法用于根据内容获取本地JSON
public abstract JSONObject getLocalJSONFromContent();
// 定义抽象方法,用于获取同步操作类型
public abstract int getSyncAction(Cursor c);
// 定义setter方法用于设置节点的属性
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;
}
// 定义getter方法用于获取节点的属性
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,190 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
// 导入所需的类和包
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
// 定义一个名为SqlData的类用于表示SQL数据
public class SqlData {
// 定义类的标签和常量
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999;
// 定义查询的投影和列索引常量
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
public static final int DATA_ID_COLUMN = 0;
public static final int DATA_MIME_TYPE_COLUMN = 1;
public static final int DATA_CONTENT_COLUMN = 2;
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
// 定义类的属性
private ContentResolver mContentResolver;
private boolean mIsCreate;
private long mDataId;
private String mDataMimeType;
private String mDataContent;
private long mDataContentData1;
private String mDataContentData3;
private ContentValues mDiffDataValues;
// 构造方法,初始化类的属性
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
// 基于现有的游标创建SqlData实例
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
// 从游标加载数据
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
// 从JSONObject设置内容
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
// 获取内容并将其转换为JSONObject
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
// 提交更改到数据库
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
mDiffDataValues.clear();
mIsCreate = false;
}
// 获取数据ID
public long getId() {
return mDataId;
}
}

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

@ -1,456 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
public class Note {
private ContentValues mNoteDiffValues;
private NoteData mNoteData;
private static final String TAG = "Note";
/**
*
* @param context
* @param folderId id
* @return
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// 创建一个新的ContentValues对象并设置相关键值对
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis(); // 获取当前时间作为创建时间
values.put(NoteColumns.CREATED_DATE, createdTime); // 存入创建时间
values.put(NoteColumns.MODIFIED_DATE, createdTime); // 存入修改时间(初始与创建时间相同)
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 存入笔记类型
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 存入本地修改状态
values.put(NoteColumns.PARENT_ID, folderId); // 存入所属文件夹id
// 向数据库中插入新笔记数据并获取包含新建笔记Uri的对象uri
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
// 从uri中获得新建笔记的id即路径segments的第二个元素并将其转换为long类型返回
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 如果获取不到这个id或者它等于-1则会抛出相应的异常
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
/**
* Note
* "Note"
*/
public Note() {
// 创建一个ContentValues对象用于保存笔记中改变的值即笔记已经被修改但还没同步到数据库
mNoteDiffValues = new ContentValues();
// 创建一个NoteData对象用于保存笔记的所有数据包括标题、正文、颜色等
mNoteData = new NoteData();
}
/**
* key-value
* @param key
* @param value
*
* ContentValuesmNoteDiffValuesNoteDatamNoteData
*
*
*/
public void setNoteValue(String key, String value) {
// 向ContentValues对象中添加键值对
mNoteDiffValues.put(key, value);
// 将本地修改状态设置为1表示笔记已经被修改但还没同步到数据库
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 将修改时间设置为当前时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
*
*
* @param key "title""content"
* @param value
*
* ContentValuesmNoteDiffValuesNoteDatamNoteData
*/
public void setTextData(String key, String value) {
// 调用mNoteData对象的setTextData方法将指定键key和值value添加到笔记的文本数据中
mNoteData.setTextData(key, value);
}
/**
* id
*
* @param id id
*
* id
* mNoteDataNoteDatasetTextDataIdid
*/
public void setTextDataId(long id) {
// 调用mNoteData对象的setTextDataId方法将指定的长整型数值作为笔记文本数据的id进行设置
mNoteData.setTextDataId(id);
}
/**
* id
*
* @return id
*
* Noteid
* mNoteDataNoteDatamTextDataIdNoteDataid
*/
public long getTextDataId() {
// 返回mNoteData对象中保存的笔记文本数据的id
return mNoteData.mTextDataId;
}
/**
* id
*
* @param id id
*
* id
* mNoteDataNoteDatasetCallDataIdid
*/
public void setCallDataId(long id) {
// 调用mNoteData对象的setCallDataId方法将指定的长整型数值作为笔记的通话记录id进行设置
mNoteData.setCallDataId(id);
}
/**
*
*
* @param key "name""number"
* @param value
*
* Note
* mNoteDataNoteDatasetCallData/
*/
public void setCallData(String key, String value) {
// 调用mNoteData对象的setCallData方法将指定键key和值value添加到笔记的通话记录数据中
mNoteData.setCallData(key, value);
}
/**
*
*
* @return
*
* NotemNoteDiffValuesmNoteData
* truefalse
*/
public boolean isLocalModified() {
// 判断mNoteDiffValues对象是否包含数据或者mNoteData对象中是否有数据被本地修改。
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
*
*
* @param context Context访
* @param noteId ID
*
* @return
*
* NoteNotetrue
* 使ContentResolvermNoteDiffValuesNoteColumns.LOCAL_MODIFIEDNoteColumns.MODIFIED_DATE
* falsemNoteDiffValuesmNoteData
* falsetrue
*/
public boolean syncNote(Context context, long noteId) {
// 检查noteId参数是否合法如果小于等于0则抛出IllegalArgumentException异常
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 如果当前Note对象没有被本地修改则直接返回true
if (!isLocalModified()) {
return true;
}
// 使用ContentResolver对象将mNoteDiffValues中保存的数值更新到NoteColumns.LOCAL_MODIFIED和NoteColumns.MODIFIED_DATE字段上
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
// 如果更新失败,则在日志中记录错误信息
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
// 清空mNoteDiffValues中的数据
mNoteDiffValues.clear();
// 将mNoteData中的数据保存到本地数据库中
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
// 如果保存失败则返回false
return false;
}
// 如果保存成功则返回true
return true;
}
private class NoteData {
private long mTextDataId;
private ContentValues mTextDataValues;
private long mCallDataId;
private ContentValues mCallDataValues;
private static final String TAG = "NoteData";
/**
*
*
* NoteDatamTextDataValuesmCallDataValuesmTextDataIdmCallDataId
* mTextDataValuesmCallDataValues
* mTextDataIdmCallDataIdIDID0
*/
public NoteData() {
// 创建一个新的ContentValues对象mTextDataValues用于保存笔记的文本数据
mTextDataValues = new ContentValues();
// 创建一个新的ContentValues对象mCallDataValues用于保存笔记的通话记录数据
mCallDataValues = new ContentValues();
// 将mTextDataId和mCallDataId的初始值设置为0
mTextDataId = 0;
mCallDataId = 0;
}
/**
*
*
* @return
*
* NoteDatamTextDataValuesmCallDataValues
* truefalse
*/
boolean isLocalModified() {
// 判断mTextDataValues和mCallDataValues对象是否包含数据如果有数据被本地修改过则返回true
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
/**
* id
*
* @param id id
*
* idid0IllegalArgumentException
* mTextDataIdNoteDataID
*/
void setTextDataId(long id) {
// 如果指定的id小于等于0则抛出IllegalArgumentException异常
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
// 将指定的长整型数值作为笔记的文本数据id进行设置
mTextDataId = id;
}
/**
* id
*
* @param id id
*
* idid0
* IllegalArgumentException
* mCallDataIdNoteDataID
*/
void setCallDataId(long id) {
// 如果指定的id小于等于0则抛出IllegalArgumentException异常
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
// 将指定的长整型数值作为笔记的通话记录数据id进行设置
mCallDataId = id;
}
/**
*
*
* @param key
* @param value
*
* mCallDataValuesNoteColumns.LOCAL_MODIFIEDNoteColumns.MODIFIED_DATE1
*
*/
void setCallData(String key, String value) {
// 将键值对保存到mCallDataValues对象中
mCallDataValues.put(key, value);
// 将NoteColumns.LOCAL_MODIFIED字段的值设置为1表示笔记已被本地修改过
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 将NoteColumns.MODIFIED_DATE字段的值更新为当前时间戳表示笔记最近一次被修改的时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
*
*
* @param key
* @param value
*
* mTextDataValuesNoteColumns.LOCAL_MODIFIEDNoteColumns.MODIFIED_DATE1
*
*/
void setTextData(String key, String value) {
// 将键值对保存到mTextDataValues对象中
mTextDataValues.put(key, value);
// 将NoteColumns.LOCAL_MODIFIED字段的值设置为1表示笔记已被本地修改过
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 将NoteColumns.MODIFIED_DATE字段的值更新为当前时间戳表示笔记最近一次被修改的时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* ContentResolver
*
* @param context Context
* @param noteId id
*
* @return Urinull
*
* NoteDataContentResolvermTextDataValuesmCallDataValuesContentResolverNoteColumns.LOCAL_MODIFIEDNoteColumns.MODIFIED_DATE0
*
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* noteId
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 初始化operationList列表和builder对象
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
// 如果mTextDataValues对象不为空则将其中的数据保存到ContentResolver中
if(mTextDataValues.size() > 0) {
// 为mTextDataValues对象添加笔记id
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
// 如果mTextDataId为0则表明当前笔记没有文本数据需要进行插入操作
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
// 通过ContentResolver插入数据并获取插入的Uri对象
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
// 解析Uri中的id值并将其设置为mTextDataId
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
// 插入失败清空mTextDataValues并返回null
mTextDataValues.clear();
return null;
}
} else {
// 如果mTextDataId不为0则表明当前笔记已经有文本数据需要进行更新操作
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
// 清空mTextDataValues对象
mTextDataValues.clear();
}
// 如果mCallDataValues对象不为空则将其中的数据保存到ContentResolver中
if(mCallDataValues.size() > 0) {
// 为mCallDataValues对象添加笔记id
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
// 如果mCallDataId为0则表明当前笔记没有通话记录数据需要进行插入操作
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
// 通过ContentResolver插入数据并获取插入的Uri对象
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
// 解析Uri中的id值并将其设置为mCallDataId
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
// 插入失败清空mCallDataValues并返回null
mCallDataValues.clear();
return null;
}
} else {
// 如果mCallDataId不为0则表明当前笔记已经有通话记录数据需要进行更新操作
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
// 清空mCallDataValues对象
mCallDataValues.clear();
}
if (operationList.size() > 0) { // 如果操作列表中有操作
try {
// 调用 ContentResolver 的 applyBatch 方法执行操作
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
// 判断操作是否成功,并返回对应的 Uri
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) { // 捕获 RemoteException 异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录异常信息到日志
return null; // 返回 null
} catch (OperationApplicationException e) { // 捕获 OperationApplicationException 异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录异常信息到日志
return null; // 返回 null
}
}
return null; // 如果操作列表为空,则返回 null
}
}
}

@ -1,629 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote {
// Note for the working note
private Note mNote;
// Note Id
private long mNoteId;
// Note content
private String mContent;
// Note mode
private int mMode;
private long mAlertDate;
private long mModifiedDate;
private int mBgColorId;
private int mWidgetId;
private int mWidgetType;
private long mFolderId;
private Context mContext;
private static final String TAG = "WorkingNote";
private boolean mIsDeleted;
private NoteSettingChangedListener mNoteSettingStatusListener;
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID, // 表示数据行的 ID
DataColumns.CONTENT, // 表示数据行的内容
DataColumns.MIME_TYPE, // 表示数据行的 MIME 类型
DataColumns.DATA1, // 表示数据行的附加数据
DataColumns.DATA2, // 表示数据行的附加数据
DataColumns.DATA3, // 表示数据行的附加数据
DataColumns.DATA4, // 表示数据行的附加数据
};
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID, // 表示笔记的父笔记 ID
NoteColumns.ALERTED_DATE, // 表示笔记的提醒时间
NoteColumns.BG_COLOR_ID, // 表示笔记的背景颜色 ID
NoteColumns.WIDGET_ID, // 表示笔记的小部件 ID
NoteColumns.WIDGET_TYPE, // 表示笔记的小部件类型
NoteColumns.MODIFIED_DATE // 表示笔记的修改时间
};
private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3;
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
private static final int NOTE_WIDGET_ID_COLUMN = 3;
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct
/**
* WorkingNote
* @param context
* @param folderId ID
*/
private WorkingNote(Context context, long folderId) {
// 初始化成员变量
mContext = context; // 上下文对象
mAlertDate = 0; // 提醒时间
mModifiedDate = System.currentTimeMillis(); // 修改时间,初始化为当前时间戳
mFolderId = folderId; // 笔记所属文件夹的 ID
mNote = new Note(); // Note 对象
mNoteId = 0; // 笔记 ID初始值为 0
mIsDeleted = false; // 是否已删除,初始值为 false
mMode = 0; // 模式,初始值为 0
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 小部件类型,初始值为无效类型
}
// Existing note construct
/**
* WorkingNote
* @param context
* @param noteId ID
* @param folderId ID
*/
private WorkingNote(Context context, long noteId, long folderId) {
// 初始化成员变量
mContext = context; // 上下文对象
mNoteId = noteId; // 笔记 ID
mFolderId = folderId; // 笔记所属文件夹的 ID
mIsDeleted = false; // 是否已删除,初始值为 false
mNote = new Note(); // Note 对象
loadNote(); // 加载笔记数据
}
/**
* 使 ID
* CONTENT_NOTE_URI ID
* mFolderIdmBgColorId
* mWidgetIdmWidgetTypemAlertDate mModifiedDate
* loadNoteData()
* IllegalArgumentException
*/
private void loadNote() {
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId),
NOTE_PROJECTION,
null,
null,
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
// 结果将填充以下变量mFolderId、mBgColorId、mWidgetId、mWidgetType、mAlertDate 和 mModifiedDate
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
}
cursor.close();
} else {
// 没有检索到任何记录,则在打印错误日志后抛出 IllegalArgumentException 异常
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
// 加载笔记剩余部分的数据
loadNoteData();
}
/**
*
*
* Content ProviderNoteNotes.CONTENT_DATA_URI
* DataColumns.NOTE_IDmNoteIdNote
* Content ProviderIDIllegalArgumentException
*/
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] { String.valueOf(mNoteId) }, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
// 遍历所有数据并将它们加载到Note对象中。
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);
}
}
/**
*
*
* WorkingNotecontextfolderIdwidgetId
* widgetTypedefaultBgColorIdWorkingNote
*
* @param context
* @param folderId ID
* @param widgetId WidgetID
* @param widgetType Widget便
* @param defaultBgColorId ID
* @return WorkingNote
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
// 创建一个空白的WorkingNote对象
WorkingNote note = new WorkingNote(context, folderId);
// 设置笔记的默认背景颜色
note.setBgColorId(defaultBgColorId);
// 设置笔记所绑定的Widget的ID
note.setWidgetId(widgetId);
// 设置笔记所绑定的Widget的类型
note.setWidgetType(widgetType);
// 返回一个新的WorkingNote对象
return note;
}
/**
* WorkingNote
*
* IDWorkingNotecontextid
* WorkingNote
*
* @param context
* @param id WorkingNoteID
* @return WorkingNote
*/
public static WorkingNote load(Context context, long id) {
// 创建一个新的WorkingNote对象并将id和0作为参数传入构造函数中
return new WorkingNote(context, id, 0);
}
/**
*
*
* WorkingNote
* WidgetWidget
*
* @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;
}
}
// 将当前WorkingNote对象同步到Note对象中
mNote.syncNote(mContext, mNoteId);
/**
* WidgetWidget
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
/**
* WorkingNote
*
* WorkingNoteexistInDatabase()
* truefalse
*
* @return truefalse
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
*
*
* WorkingNote
* isWorthSaving()
* falsetrue
*
* @return truefalse
*/
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
// 如果该笔记已经被标记为“已删除”,或者它既没有保存到数据库中,也没有任何数据,或者它已经存在于
// 数据库中但没有被修改过,则认为该笔记不需要被保存。
return false;
} else {
return true;
}
}
/**
*
*
* NoteSettingChangedListener便
*
* @param l
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
/**
*
*
* WorkingNotedateset
*
* Note
*
* @param date
* @param set
*/
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
// 如果待设置的提醒日期与当前笔记的提醒日期不同则需要更新Note对象中的数据。
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
// 通知设置状态更改监听器。
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
/**
*
*
* WorkingNotemarkmarktrue
*
* WidgetWidget
*
* @param mark WorkingNote
*/
public void markDeleted(boolean mark) {
// 标记当前WorkingNote对象是否已经被删除。
mIsDeleted = mark;
// 检查是否存在与该笔记绑定的Widget如果有则需要更新Widget的内容。
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
/**
* ID
*
* WorkingNoteIDid
* IDIDIDNote
*
*
* @param id ID
*/
public void setBgColorId(int id) {
if (id != mBgColorId) {
// 如果待设置的背景颜色ID与当前笔记的背景颜色ID不同则需要更新Note对象中的数据。
mBgColorId = id;
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
// 通知设置状态更改监听器。
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
}
}
/**
*
*
* WorkingNotemode
* Note
*
* @param mode
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
// 如果当前笔记的清单模式与待设置的清单模式不同则需要更新Note对象中的数据。
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
// 通知设置状态更改监听器。
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
}
}
/**
* Widget
*
* WorkingNoteWidgettype
* WidgetWidgetWidgetNote
*
* @param type Widget
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
// 如果当前笔记绑定的Widget类型与待设置的Widget类型不同则需要更新Note对象中的数据。
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
/**
* Widget ID
*
* WorkingNoteWidget IDid
* Widget IDWidget IDWidget IDNote
*
* @param id Widget ID
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
// 如果当前笔记绑定的Widget ID与待设置的Widget ID不同则需要更新Note对象中的数据。
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
/**
*
*
* WorkingNotetext
* Note
*
* @param text
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
// 如果当前笔记的文本内容与待设置的文本内容不同则需要更新Note对象中的数据。
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
/**
*
*
* WorkingNotephoneNumbercallDate
* Note
*
* @param phoneNumber
* @param callDate
*/
public void convertToCallNote(String phoneNumber, long callDate) {
// 将笔记的类型设置为通话记录并将相应的数据保存到Note对象中。
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));
}
/**
*
*
* WorkingNotetruefalse
*
* @return truefalse
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
/**
*
*
* WorkingNote
*
* @return WorkingNote
*/
public String getContent() {
return mContent;
}
/**
*
*
* WorkingNote
*
* @return WorkingNote
*/
public long getAlertDate() {
return mAlertDate;
}
/**
*
*
* WorkingNote
*
* @return WorkingNote
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* ID
*
* WorkingNoteIDIDID
*
* @return WorkingNoteID
*/
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
/**
* ID
*
* WorkingNoteID
*
* @return WorkingNoteID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* ID
*
* WorkingNoteIDIDID
*
* @return WorkingNoteID
*/
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
/**
*
*
* WorkingNote
*
* @return WorkingNote
*/
public int getCheckListMode() {
return mMode;
}
/**
* ID
*
* WorkingNoteNoteID
*
* @return WorkingNoteNoteID
*/
public long getNoteId() {
return mNoteId;
}
public long getFolderId() {
return mFolderId;
}
public int getWidgetId() {
return mWidgetId;
}
public int getWidgetType() {
return mWidgetType;
}
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
*/
void onBackgroundColorChanged();
/**
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -1,442 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
/**
* BackupUtils 线
* @param context
* @return BackupUtils
*/
public static synchronized BackupUtils getInstance(Context context) {
// 如果实例不存在,创建一个新的实例
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
// 返回现有实例或新创建的实例
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
// SD卡未挂载的状态
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 备份文件不存在的状态
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 数据格式不正确,可能被其他程序更改的状态
public static final int STATE_DATA_DESTROIED = 2;
// 恢复或备份失败的状态,由运行时异常引起
public static final int STATE_SYSTEM_ERROR = 3;
// 备份或恢复成功的状态
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport; // 用于导出文本数据的 TextExport 实例
// 构造函数,用于创建 BackupUtils 类的实例
private BackupUtils(Context context) {
mTextExport = new TextExport(context); // 创建 TextExport 实例并保存到成员变量中
}
/**
*
* @return true false
*/
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
*
* @return
*/
public int exportToText() {
return mTextExport.exportToText();
}
/**
*
* @return
*/
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
/**
*
* @return
*/
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID, // 笔记的标识符
NoteColumns.MODIFIED_DATE, // 笔记最近修改的日期
NoteColumns.SNIPPET, // 笔记内容的片段,用于快速预览笔记
NoteColumns.TYPE // 笔记的类型
};
private static final int NOTE_COLUMN_ID = 0; // 列 ID
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; // 列 MODIFIED_DATE
private static final int NOTE_COLUMN_SNIPPET = 2; // 列 SNIPPET
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT, // 数据的主体内容
DataColumns.MIME_TYPE, // 数据的 MIME 类型
DataColumns.DATA1, // 此列可能包含电话号码、电子邮件地址等。
DataColumns.DATA2, // 此列可能包含电话类型(家庭、工作等)、电子邮件类型等。
DataColumns.DATA3, // 通常情况下,此列的使用与 DATA2 相同。
DataColumns.DATA4 // 保留供将来使用。
};
private static final int DATA_COLUMN_CONTENT = 0; // 列 CONTENT
private static final int DATA_COLUMN_MIME_TYPE = 1; // 列 MIME_TYPE
private static final int DATA_COLUMN_CALL_DATE = 2; // 列 CALL_DATE
private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 列 PHONE_NUMBER
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; // 导出文件所在的目录
/**
* TextExport
*
* @param context
*/
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); // 从资源中获取导出文本的格式
mContext = context; // 保存应用程序上下文
mFileName = ""; // 导出文件名称为空
mFileDirectory = ""; // 导出文件所在目录为空
}
/**
*
*
* @param id
* @return
*/
private String getFormat(int id) {
return TEXT_FORMAT[id]; // 返回指定索引处的导出文本格式
}
/**
* Export the folder identified by folder id to text
*/
/**
*
*
* @param folderId
* @param ps PrintStream
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询属于该笔记本的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[]{folderId}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// 打印笔记的最近修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于该笔记的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps); // 将笔记导出到文本文件
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* ID
*
* @param noteId ID
* @param ps
*/
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询与该笔记关联的数据项
Cursor dataCursor = mContext.getContentResolver().query(
Notes.CONTENT_DATA_URI, // 查询的 URI
DATA_PROJECTION, // 要查询的列名
DataColumns.NOTE_ID + "=?", // WHERE 子句
new String[]{noteId}, // WHERE 子句中占位符的值
null // 排序方式
);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
// 获取当前数据项的 MIME 类型
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// 处理通话记录类型的笔记
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); // 电话号码
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); // 通话日期
String location = dataCursor.getString(DATA_COLUMN_CONTENT); // 通话位置
if (!TextUtils.isEmpty(phoneNumber)) {
// 如果电话号码不为空,就将其写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber));
}
// 将通话日期格式化为字符串,并写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
DateFormat.format(mContext.getString(R.string.format_datetime_mdhm), callDate)));
if (!TextUtils.isEmpty(location)) {
// 如果通话位置不为空,就将其写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
// 处理普通笔记类型
String content = dataCursor.getString(DATA_COLUMN_CONTENT); // 笔记内容
if (!TextUtils.isEmpty(content)) {
// 如果笔记内容不为空,就将其写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), content));
}
}
} while (dataCursor.moveToNext());
}
// 关闭游标
dataCursor.close();
}
// 在每个笔记之间输出一个分隔符
try {
ps.write(new byte[]{Character.LINE_SEPARATOR, Character.LETTER_NUMBER});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
*
*
* @return
*/
public int exportToText() {
// 检查外部存储是否可用
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
// 获取打印流对象,准备写入数据
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 先导出所有的文件夹及其笔记
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 查询的 URI
NOTE_PROJECTION, // 要查询的列名
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, // WHERE 子句
null, // WHERE 子句中占位符的值
null // 排序方式
);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// 打印文件夹名称
String folderName = "";
if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
// 导出当前文件夹下的所有笔记
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
// 关闭游标
folderCursor.close();
}
// 导出根目录下的笔记
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 查询的 URI
NOTE_PROJECTION, // 要查询的列名
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=0", // WHERE 子句
null, // WHERE 子句中占位符的值
null // 排序方式
);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
// 打印笔记的最后修改时间
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 导出当前笔记的内容
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
// 关闭游标
noteCursor.close();
}
// 关闭打印流
ps.close();
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
/**
* PrintStream
*
* @return PrintStream
*/
private PrintStream getExportToTextPrintStream() {
// 生成指定路径和名称的文本文件保存在SD卡上。
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
// 如果创建文件失败则记录错误日志并返回null。
Log.e(TAG, "create file to exported failed");
return null;
}
// 将文件名保存在成员变量中。
mFileName = file.getName();
// 将文件目录路径保存在成员变量中。
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
// 创建一个新的FileOutputStream对象并将其包装在PrintStream对象中。
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
// 如果找不到文件则打印堆栈跟踪并返回null。
e.printStackTrace();
return null;
} catch (NullPointerException e) {
// 如果发生空指针异常则打印堆栈跟踪并返回null。
e.printStackTrace();
return null;
}
// 返回用于将数据输出到文本文件中的PrintStream对象。
return ps;
}
}
/**
* 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对象用于拼接文件路径和名称。
StringBuilder sb = new StringBuilder();
// 将外部存储器目录添加到StringBuilder中。
sb.append(Environment.getExternalStorageDirectory());
// 将文件路径的字符串资源ID添加到StringBuilder中。
sb.append(context.getString(filePathResId));
// 创建一个包含文件路径的File对象。
File filedir = new File(sb.toString());
// 将文件名格式化并将其添加到StringBuilder末尾。
sb.append(context.getString(fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd), System.currentTimeMillis())));
// 创建一个包含完整文件路径的File对象。
File file = new File(sb.toString());
try {
// 如果文件夹不存在,则创建它。
if (!filedir.exists()) {
filedir.mkdir();
}
// 如果文件不存在,则创建它。
if (!file.exists()) {
file.createNewFile();
}
// 返回创建的文件对象。
return file;
} catch (SecurityException e) {
// 捕获安全异常,并打印错误日志。
e.printStackTrace();
} catch (IOException e) {
// 捕获I/O异常并打印错误日志。
e.printStackTrace();
}
// 如果创建文件失败则返回null。
return null;
}
}

@ -1,284 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
public static final int BG_DEFAULT_COLOR = YELLOW;
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
public static class NoteBgResources {
// 存储五种不同颜色的背景图片资源id
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow, // 黄色背景图片
R.drawable.edit_blue, // 蓝色背景图片
R.drawable.edit_white, // 白色背景图片
R.drawable.edit_green, // 绿色背景图片
R.drawable.edit_red // 红色背景图片
};
// 存储五种不同颜色的标题栏背景图片资源id
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow, // 黄色标题栏背景图片
R.drawable.edit_title_blue, // 蓝色标题栏背景图片
R.drawable.edit_title_white, // 白色标题栏背景图片
R.drawable.edit_title_green, // 绿色标题栏背景图片
R.drawable.edit_title_red // 红色标题栏背景图片
};
/**
* id ID
*
* @param id ID 0 4 绿
* @return ID
*/
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
/**
* id ID
*
* @param id ID 0 2 绿
* @return ID
*/
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
/**
* ID
*
* @param context
* @return ID
*/
public static int getDefaultBgId(Context context) {
// 检查应用程序的共享首选项中是否启用了自定义笔记项背景颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 如果已经启用,则生成一个随机数并返回对应的笔记项背景资源 ID
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
// 如果未启用,则返回默认背景颜色(即 BG_DEFAULT_COLOR所对应的资源 ID。
return BG_DEFAULT_COLOR;
}
}
public static class NoteItemBgResources {
/**
* ID
*/
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
/**
* ID
*/
private final static int [] BG_NORMAL_RESOURCES = new int [] {
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
/**
* ID
*/
private final static int [] BG_LAST_RESOURCES = new int [] {
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
/**
* ID
*/
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
/**
* id ID
*
* @param id ID 0 4 绿
* @return ID
*/
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
/**
* id ID
*
* @param id ID 0 4 绿
* @return ID
*/
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
/**
* id ID
*
* @param id ID 0 4 绿
* @return ID
*/
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
/**
* id ID
*
* @param id ID 0 4 绿
* @return ID
*/
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
/**
* ID drawableR.drawable.list_folder
*
* @return ID
*/
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
public static class WidgetBgResources {
/**
* 2x2 ID
*/
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
/**
* id 2x2 ID
*
* @param id ID 0 4 绿
* @return id 2x2 ID
*/
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
/**
* 4x4 ID
*/
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
/**
* id 4x4 ID
*
* @param id ID 0 4 绿
* @return id 4x4 ID
*/
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
public static class TextAppearanceResources {
/**
* ID
*/
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
/**
* id ID
*
* @param id ID 0 3 NormalMediumLargeSuper
* @return id ID
*/
public static int getTexAppearanceResource(int id) {
/**
* HACKME: ID bug
* id
* {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
/**
* ID
*
* @return ID
*/
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

Binary file not shown.

Binary file not shown.
Loading…
Cancel
Save