main
parent
5bc006e7d7
commit
e14c79eccf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.data;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import net.micode.notes.data.Notes.DataColumns;
|
||||
import net.micode.notes.data.Notes.DataConstants;
|
||||
import net.micode.notes.data.Notes.NoteColumns;
|
||||
|
||||
public class NotesDatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String DB_NAME = "note.db";
|
||||
private static final int DB_VERSION = 4;
|
||||
public interface TABLE {
|
||||
public static final String NOTE = "note";
|
||||
public static final String DATA = "data";
|
||||
}
|
||||
private static final String TAG = "NotesDatabaseHelper";
|
||||
private static NotesDatabaseHelper mInstance;
|
||||
|
||||
// 创建笔记表的SQL语句
|
||||
private static final String CREATE_NOTE_TABLE_SQL =
|
||||
"CREATE TABLE " + TABLE.NOTE + "(" +
|
||||
NoteColumns.ID + " INTEGER PRIMARY KEY," +
|
||||
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
|
||||
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
|
||||
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
|
||||
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
|
||||
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
|
||||
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
|
||||
")";
|
||||
|
||||
// 创建数据表的SQL语句
|
||||
private static final String CREATE_DATA_TABLE_SQL =
|
||||
"CREATE TABLE " + TABLE.DATA + "(" +
|
||||
DataColumns.ID + " INTEGER PRIMARY KEY," +
|
||||
DataColumns.MIME_TYPE + " TEXT NOT NULL," +
|
||||
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
|
||||
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
|
||||
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
|
||||
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +
|
||||
DataColumns.DATA1 + " INTEGER," +
|
||||
DataColumns.DATA2 + " INTEGER," +
|
||||
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
|
||||
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
|
||||
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
|
||||
")";
|
||||
|
||||
// 创建笔记数据ID索引的SQL语句
|
||||
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
|
||||
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
|
||||
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
|
||||
|
||||
// 笔记表触发器SQL语句
|
||||
// 笔记移动到文件夹时增加文件夹笔记计数
|
||||
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
|
||||
"CREATE TRIGGER increase_folder_count_on_update " +
|
||||
"AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
|
||||
" BEGIN " +
|
||||
" UPDATE " + TABLE.NOTE +
|
||||
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
|
||||
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
|
||||
" END";
|
||||
|
||||
// 笔记从文件夹移动时减少文件夹笔记计数
|
||||
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
|
||||
"CREATE TRIGGER decrease_folder_count_on_update " +
|
||||
"AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
|
||||
" BEGIN " +
|
||||
" UPDATE " + TABLE.NOTE +
|
||||
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
|
||||
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
|
||||
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
|
||||
" END";
|
||||
|
||||
// 删除笔记时从文件夹减少笔记计数
|
||||
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
|
||||
"CREATE TRIGGER decrease_folder_count_on_delete " +
|
||||
"AFTER DELETE ON " + TABLE.NOTE +
|
||||
" BEGIN " +
|
||||
" UPDATE " + TABLE.NOTE +
|
||||
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
|
||||
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
|
||||
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
|
||||
" END";
|
||||
|
||||
// 数据表触发器SQL语句
|
||||
// 插入数据时更新笔记内容
|
||||
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
|
||||
"CREATE TRIGGER update_note_content_on_insert " +
|
||||
"AFTER INSERT ON " + TABLE.DATA +
|
||||
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
|
||||
" BEGIN" +
|
||||
" UPDATE " + TABLE.NOTE +
|
||||
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
|
||||
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
|
||||
" END";
|
||||
|
||||
// 更新数据时更新笔记内容
|
||||
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
|
||||
"CREATE TRIGGER update_note_content_on_update " +
|
||||
"AFTER UPDATE ON " + TABLE.DATA +
|
||||
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
|
||||
" BEGIN" +
|
||||
" UPDATE " + TABLE.NOTE +
|
||||
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
|
||||
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
|
||||
" END";
|
||||
|
||||
// 删除数据时更新笔记内容
|
||||
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
|
||||
"CREATE TRIGGER update_note_content_on_delete " +
|
||||
"AFTER DELETE ON " + TABLE.DATA +
|
||||
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
|
||||
" BEGIN" +
|
||||
" UPDATE " + TABLE.NOTE +
|
||||
" SET " + NoteColumns.SNIPPET + "=''" +
|
||||
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
|
||||
" END";
|
||||
|
||||
// 删除笔记时同时删除其数据
|
||||
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
|
||||
"CREATE TRIGGER delete_data_on_delete " +
|
||||
"AFTER DELETE ON " + TABLE.NOTE +
|
||||
" BEGIN" +
|
||||
" DELETE FROM " + TABLE.DATA +
|
||||
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
|
||||
" END";
|
||||
|
||||
// 删除文件夹时同时删除其下所有笔记
|
||||
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
|
||||
"CREATE TRIGGER folder_delete_notes_on_delete " +
|
||||
"AFTER DELETE ON " + TABLE.NOTE +
|
||||
" BEGIN" +
|
||||
" DELETE FROM " + TABLE.NOTE +
|
||||
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
|
||||
" END";
|
||||
|
||||
// 移动文件夹到垃圾桶时移动其下所有笔记
|
||||
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
|
||||
"CREATE TRIGGER folder_move_notes_on_trash " +
|
||||
"AFTER UPDATE ON " + TABLE.NOTE +
|
||||
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
|
||||
" BEGIN" +
|
||||
" UPDATE " + TABLE.NOTE +
|
||||
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
|
||||
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
|
||||
" END";
|
||||
|
||||
public NotesDatabaseHelper(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
}
|
||||
|
||||
public void createNoteTable(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_NOTE_TABLE_SQL);
|
||||
reCreateNoteTableTriggers(db);
|
||||
createSystemFolder(db);
|
||||
Log.d(TAG, "note table has been created");
|
||||
}
|
||||
|
||||
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
|
||||
// 删除旧的触发器
|
||||
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
|
||||
|
||||
// 创建新的触发器
|
||||
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
|
||||
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
|
||||
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
|
||||
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
|
||||
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
|
||||
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
|
||||
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
|
||||
}
|
||||
|
||||
private void createSystemFolder(SQLiteDatabase db) {
|
||||
ContentValues values = new ContentValues();
|
||||
// 创建通话记录文件夹
|
||||
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
|
||||
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
||||
db.insert(TABLE.NOTE, null, values);
|
||||
|
||||
// 创建根文件夹,即默认文件夹
|
||||
values.clear();
|
||||
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
|
||||
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
||||
db.insert(TABLE.NOTE, null, values);
|
||||
|
||||
// 创建临时文件夹,用于移动笔记
|
||||
values.clear();
|
||||
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
|
||||
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
||||
db.insert(TABLE.NOTE, null, values);
|
||||
|
||||
// 创建垃圾箱文件夹
|
||||
values.clear();
|
||||
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
|
||||
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
||||
db.insert(TABLE.NOTE, null, values);
|
||||
}
|
||||
|
||||
public void createDataTable(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_DATA_TABLE_SQL);
|
||||
reCreateDataTableTriggers(db);
|
||||
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
|
||||
Log.d(TAG, "data table has been created");
|
||||
}
|
||||
|
||||
private void reCreateDataTableTriggers(SQLiteDatabase db) {
|
||||
// 删除旧的触发器
|
||||
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
|
||||
|
||||
// 创建新的触发器
|
||||
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
|
||||
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
|
||||
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
|
||||
}
|
||||
|
||||
static synchronized NotesDatabaseHelper getInstance(Context context) {
|
||||
if (mInstance == null) {
|
||||
mInstance = new NotesDatabaseHelper(context);
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
createNoteTable(db);
|
||||
createDataTable(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
boolean reCreateTriggers = false;
|
||||
boolean skipV2 = false;
|
||||
|
||||
if (oldVersion == 1) {
|
||||
upgradeToV2(db);
|
||||
skipV2 = true; // 升级到版本2包含了从版本1到版本2的升级
|
||||
oldVersion++;
|
||||
}
|
||||
|
||||
if (oldVersion == 2 && !skipV2) {
|
||||
upgradeToV3(db);
|
||||
reCreateTriggers = true;
|
||||
oldVersion++;
|
||||
}
|
||||
|
||||
if (oldVersion == 3) {
|
||||
upgradeToV4(db);
|
||||
oldVersion++;
|
||||
}
|
||||
|
||||
if (reCreateTriggers) {
|
||||
reCreateNoteTableTriggers(db);
|
||||
reCreateDataTableTriggers(db);
|
||||
}
|
||||
|
||||
if (oldVersion != newVersion) {
|
||||
throw new IllegalStateException("Upgrade notes database to version " + newVersion +
|
||||
" fails");
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeToV2(SQLiteDatabase db) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
|
||||
createNoteTable(db);
|
||||
createDataTable(db);
|
||||
}
|
||||
|
||||
private void upgradeToV3(SQLiteDatabase db) {
|
||||
// 删除未使用的触发器
|
||||
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
|
||||
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
|
||||
// 为笔记表添加gtask_id列
|
||||
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID +
|
||||
" TEXT NOT NULL DEFAULT ''");
|
||||
// 添加垃圾箱系统文件夹
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
|
||||
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
||||
db.insert(TABLE.NOTE, null, values);
|
||||
}
|
||||
|
||||
private void upgradeToV4(SQLiteDatabase db) {
|
||||
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION +
|
||||
" INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
Binary file not shown.
@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.gtask.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.data.Notes.DataColumns;
|
||||
import net.micode.notes.data.Notes.DataConstants;
|
||||
import net.micode.notes.data.Notes.NoteColumns;
|
||||
import net.micode.notes.gtask.exception.ActionFailureException;
|
||||
import net.micode.notes.tool.GTaskStringUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Task extends Node {
|
||||
// 日志标签
|
||||
private static final String TAG = Task.class.getSimpleName();
|
||||
|
||||
// 任务完成状态
|
||||
private boolean mCompleted;
|
||||
|
||||
// 任务笔记内容
|
||||
private String mNotes;
|
||||
|
||||
// 任务元信息
|
||||
private JSONObject mMetaInfo;
|
||||
|
||||
// 前一个任务兄弟节点
|
||||
private Task mPriorSibling;
|
||||
|
||||
// 父任务列表
|
||||
private TaskList mParent;
|
||||
|
||||
// 构造函数
|
||||
public Task() {
|
||||
super();
|
||||
mCompleted = false;
|
||||
mNotes = null;
|
||||
mPriorSibling = null;
|
||||
mParent = null;
|
||||
mMetaInfo = null;
|
||||
}
|
||||
|
||||
// 生成创建任务的操作
|
||||
@Override
|
||||
public JSONObject getCreateAction(int actionId) {
|
||||
JSONObject js = new JSONObject();
|
||||
try {
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
|
||||
JSONObject entity = new JSONObject();
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_TASK);
|
||||
if (getNotes() != null) {
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
|
||||
}
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
|
||||
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
|
||||
if (mPriorSibling != null) {
|
||||
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
throw new ActionFailureException("fail to generate task-create jsonobject");
|
||||
}
|
||||
return js;
|
||||
}
|
||||
|
||||
// 生成更新任务的操作
|
||||
@Override
|
||||
public JSONObject getUpdateAction(int actionId) {
|
||||
JSONObject js = new JSONObject();
|
||||
try {
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
|
||||
JSONObject entity = new JSONObject();
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
|
||||
if (getNotes() != null) {
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
|
||||
}
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
throw new ActionFailureException("fail to generate task-update jsonobject");
|
||||
}
|
||||
return js;
|
||||
}
|
||||
|
||||
// 通过远端JSON设置任务内容
|
||||
@Override
|
||||
public void setContentByRemoteJSON(JSONObject js) {
|
||||
if (js != null) {
|
||||
try {
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
|
||||
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
|
||||
}
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
|
||||
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
|
||||
}
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
|
||||
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
|
||||
}
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
|
||||
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
|
||||
}
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
|
||||
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
|
||||
}
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
|
||||
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
throw new ActionFailureException("fail to get task content from jsonobject");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 通过本地JSON设置任务内容
|
||||
@Override
|
||||
public void setContentByLocalJSON(JSONObject js) {
|
||||
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|
||||
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
|
||||
Log.w(TAG, "setContentByLocalJSON: nothing is available");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
|
||||
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
|
||||
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
|
||||
Log.e(TAG, "invalid type");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < dataArray.length(); i++) {
|
||||
JSONObject data = dataArray.getJSONObject(i);
|
||||
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
|
||||
setName(data.getString(DataColumns.CONTENT));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// 从未同步的内容中获取本地JSON
|
||||
@Override
|
||||
public JSONObject getLocalJSONFromContent() {
|
||||
try {
|
||||
if (mMetaInfo == null) {
|
||||
JSONObject js = new JSONObject();
|
||||
JSONObject note = new JSONObject();
|
||||
JSONArray dataArray = new JSONArray();
|
||||
JSONObject data = new JSONObject();
|
||||
data.put(DataColumns.CONTENT, getName());
|
||||
dataArray.put(data);
|
||||
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
|
||||
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
|
||||
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
|
||||
return js;
|
||||
} else {
|
||||
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
|
||||
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
|
||||
for (int i = 0; i < dataArray.length(); i++) {
|
||||
JSONObject data = dataArray.getJSONObject(i);
|
||||
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
|
||||
data.put(DataColumns.CONTENT, getName());
|
||||
}
|
||||
}
|
||||
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
|
||||
return mMetaInfo;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置任务元信息
|
||||
public void setMetaInfo(MetaData metaData) {
|
||||
if (metaData != null && metaData.getNotes() != null) {
|
||||
try {
|
||||
mMetaInfo = new JSONObject(metaData.getNotes());
|
||||
} catch (JSONException e) {
|
||||
Log.w(TAG, e.toString());
|
||||
mMetaInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确定同步操作类型
|
||||
@Override
|
||||
public int getSyncAction(Cursor c) {
|
||||
try {
|
||||
JSONObject noteInfo = null;
|
||||
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
|
||||
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
|
||||
}
|
||||
if (noteInfo == null) {
|
||||
Log.w(TAG, "it seems that note meta has been deleted");
|
||||
return SYNC_ACTION_UPDATE_REMOTE;
|
||||
}
|
||||
if (!noteInfo.has(NoteColumns.ID)) {
|
||||
Log.w(TAG, "remote note id seems to be deleted");
|
||||
return SYNC_ACTION_UPDATE_LOCAL;
|
||||
}
|
||||
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
|
||||
Log.w(TAG, "note id doesn't match");
|
||||
return SYNC_ACTION_UPDATE_LOCAL;
|
||||
}
|
||||
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
|
||||
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
|
||||
return SYNC_ACTION_NONE;
|
||||
} else {
|
||||
return SYNC_ACTION_UPDATE_LOCAL;
|
||||
}
|
||||
} else {
|
||||
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
|
||||
Log.e(TAG, "gtask id doesn't match");
|
||||
return SYNC_ACTION_ERROR;
|
||||
}
|
||||
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
|
||||
return SYNC_ACTION_UPDATE_REMOTE;
|
||||
} else {
|
||||
return SYNC_ACTION_UPDATE_CONFLICT;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return SYNC_ACTION_ERROR;
|
||||
}
|
||||
|
||||
// 判断任务是否值得保存
|
||||
|
||||
@Override
|
||||
public boolean isWorthSaving() {
|
||||
return mMetaInfo != null || (getName() != null && !getName().trim().isEmpty())
|
||||
|| (getNotes() != null && !getNotes().trim().isEmpty());
|
||||
}
|
||||
// 设置任务完成状态
|
||||
public void setCompleted(boolean completed) {
|
||||
this.mCompleted = completed;
|
||||
}
|
||||
|
||||
// 设置任务笔记内容
|
||||
public void setNotes(String notes) {
|
||||
this.mNotes = notes;
|
||||
}
|
||||
|
||||
// 设置前一个任务兄弟节点
|
||||
public void setPriorSibling(Task priorSibling) {
|
||||
this.mPriorSibling = priorSibling;
|
||||
}
|
||||
|
||||
// 设置父任务列表
|
||||
public void setParent(TaskList parent) {
|
||||
this.mParent = parent;
|
||||
}
|
||||
|
||||
// 获取任务完成状态
|
||||
public boolean getCompleted() {
|
||||
return this.mCompleted;
|
||||
}
|
||||
|
||||
// 获取任务笔记内容
|
||||
public String getNotes() {
|
||||
return this.mNotes;
|
||||
}
|
||||
|
||||
// 获取前一个任务兄弟节点
|
||||
public Task getPriorSibling() {
|
||||
return this.mPriorSibling;
|
||||
}
|
||||
|
||||
// 获取父任务列表
|
||||
public TaskList getParent() {
|
||||
return this.mParent;
|
||||
}
|
@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.gtask.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.data.Notes.NoteColumns;
|
||||
import net.micode.notes.gtask.exception.ActionFailureException;
|
||||
import net.micode.notes.tool.GTaskStringUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class TaskList extends Node {
|
||||
// 日志标签
|
||||
private static final String TAG = TaskList.class.getSimpleName();
|
||||
|
||||
// 任务列表索引
|
||||
private int mIndex;
|
||||
|
||||
// 子任务列表
|
||||
private ArrayList<Task> mChildren;
|
||||
|
||||
// 构造函数
|
||||
public TaskList() {
|
||||
super();
|
||||
mChildren = new ArrayList<Task>();
|
||||
mIndex = 1; // 默认索引为1
|
||||
}
|
||||
|
||||
// 生成创建任务列表的操作
|
||||
@Override
|
||||
public JSONObject getCreateAction(int actionId) {
|
||||
JSONObject js = new JSONObject();
|
||||
try {
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
|
||||
JSONObject entity = new JSONObject();
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
|
||||
}
|
||||
return js;
|
||||
}
|
||||
|
||||
// 生成更新任务列表的操作
|
||||
@Override
|
||||
public JSONObject getUpdateAction(int actionId) {
|
||||
JSONObject js = new JSONObject();
|
||||
try {
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
|
||||
JSONObject entity = new JSONObject();
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
|
||||
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
|
||||
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
|
||||
}
|
||||
return js;
|
||||
}
|
||||
|
||||
// 通过远端JSON设置任务列表内容
|
||||
@Override
|
||||
public void setContentByRemoteJSON(JSONObject js) {
|
||||
if (js != null) {
|
||||
try {
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
|
||||
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
|
||||
}
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
|
||||
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
|
||||
}
|
||||
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
|
||||
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
throw new ActionFailureException("fail to get tasklist content from jsonobject");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 通过本地JSON设置任务列表内容
|
||||
@Override
|
||||
public void setContentByLocalJSON(JSONObject js) {
|
||||
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
|
||||
Log.w(TAG, "setContentByLocalJSON: nothing is available");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
|
||||
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
|
||||
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + folder.getString(NoteColumns.SNIPPET));
|
||||
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
|
||||
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) {
|
||||
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
|
||||
} else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) {
|
||||
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE);
|
||||
} else {
|
||||
Log.e(TAG, "invalid system folder");
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "error type");
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// 从未同步的内容中获取本地JSON
|
||||
@Override
|
||||
public JSONObject getLocalJSONFromContent() {
|
||||
try {
|
||||
JSONObject js = new JSONObject();
|
||||
JSONObject folder = new JSONObject();
|
||||
String folderName = getName();
|
||||
if (folderName.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) {
|
||||
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length());
|
||||
}
|
||||
folder.put(NoteColumns.SNIPPET, folderName);
|
||||
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) {
|
||||
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
||||
} else {
|
||||
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
|
||||
}
|
||||
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
|
||||
return js;
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 确定同步操作类型
|
||||
@Override
|
||||
public int getSyncAction(Cursor c) {
|
||||
try {
|
||||
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
|
||||
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
|
||||
return SYNC_ACTION_NONE;
|
||||
} else {
|
||||
return SYNC_ACTION_UPDATE_LOCAL;
|
||||
}
|
||||
} else {
|
||||
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
|
||||
Log.e(TAG, "gtask id doesn't match");
|
||||
return SYNC_ACTION_ERROR;
|
||||
}
|
||||
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
|
||||
return SYNC_ACTION_UPDATE_REMOTE;
|
||||
} else {
|
||||
return SYNC_ACTION_UPDATE_CONFLICT;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return SYNC_ACTION_ERROR;
|
||||
}
|
||||
|
||||
// 获取子任务数量
|
||||
public int getChildTaskCount() {
|
||||
return mChildren.size();
|
||||
}
|
||||
|
||||
// 添加子任务
|
||||
public boolean addChildTask(Task task) {
|
||||
boolean ret = false;
|
||||
if (task != null && !mChildren.contains(task)) {
|
||||
ret = mChildren.add(task);
|
||||
if (ret) {
|
||||
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren.get(mChildren.size() - 1));
|
||||
task.setParent(this);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 在指定位置添加子任务
|
||||
public boolean addChildTask(Task task, int index) {
|
||||
if (index < 0 || index > mChildren.size()) {
|
||||
Log.e(TAG, "add child task: invalid index");
|
||||
return false;
|
||||
}
|
||||
int pos = mChildren.indexOf(task);
|
||||
if (task != null && pos == -1) {
|
||||
mChildren.add(index, task);
|
||||
Task preTask = null;
|
||||
Task afterTask = null;
|
||||
if (index != 0) {
|
||||
preTask = mChildren.get(index - 1);
|
||||
}
|
||||
if (index != mChildren.size() - 1) {
|
||||
afterTask = mChildren.get(index + 1);
|
||||
}
|
||||
task.setPriorSibling(preTask);
|
||||
if (afterTask != null) {
|
||||
afterTask.setPriorSibling(task);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 移除子任务
|
||||
public boolean removeChildTask(Task task) {
|
||||
boolean ret = false;
|
||||
int index = mChildren.indexOf(task);
|
||||
if (index != -1) {
|
||||
ret = mChildren.remove(task);
|
||||
if (ret) {
|
||||
task.setPriorSibling(null);
|
||||
task.setParent(null);
|
||||
if (index != mChildren.size()) {
|
||||
mChildren.get(index).setPriorSibling(index == 0 ? null : mChildren.get(index - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 移动子任务到新的位置
|
||||
public boolean moveChildTask(Task task, int index) {
|
||||
if (index < 0 || index >= mChildren.size()) {
|
||||
Log.e(TAG, "move child task: invalid index");
|
||||
return false;
|
||||
}
|
||||
int pos = mChildren.indexOf(task);
|
||||
if (pos == -1) {
|
||||
Log.e(TAG, "move child task: the task should in the list");
|
||||
return false;
|
||||
}
|
||||
if (pos == index) {
|
||||
return true;
|
||||
}
|
||||
return (removeChildTask(task) && addChildTask(task, index));
|
||||
}
|
||||
|
||||
// 根据全局唯一标识查找子任务
|
||||
public Task findChildTaskByGid(String gid) {
|
||||
for (Task t : mChildren) {
|
||||
if (t.getGid().equals(gid)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据子任务
|
||||
获取索引
|
||||
public int getChildTaskIndex(Task task) {
|
||||
return mChildren.indexOf(task);
|
||||
}
|
||||
// 根据索引获取子任务
|
||||
public Task getChildTaskByIndex(int index) {
|
||||
if (index < 0 || index >= mChildren.size()) {
|
||||
Log.e(TAG, "getTaskByIndex: invalid index");
|
||||
return null;
|
||||
}
|
||||
return mChildren.get(index);
|
||||
}
|
||||
|
||||
// 根据全局唯一标识获取子任务
|
||||
public Task getChilTaskByGid(String gid) {
|
||||
for (Task task : mChildren) {
|
||||
if (task.getGid().equals(gid)) {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取所有子任务列表
|
||||
public ArrayList<Task> getChildTaskList() {
|
||||
return this.mChildren;
|
||||
}
|
||||
|
||||
// 设置索引
|
||||
public void setIndex(int index) {
|
||||
this.mIndex = index;
|
||||
}
|
||||
|
||||
// 获取索引
|
||||
public int getIndex() {
|
||||
return this.mIndex;
|
||||
}
|
@ -0,0 +1,785 @@
|
||||
|
||||
package net.micode.notes.gtask.remote;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.data.Notes.DataColumns;
|
||||
import net.micode.notes.data.Notes.NoteColumns;
|
||||
import net.micode.notes.gtask.data.MetaData;
|
||||
import net.micode.notes.gtask.data.Node;
|
||||
import net.micode.notes.gtask.data.SqlNote;
|
||||
import net.micode.notes.gtask.data.Task;
|
||||
import net.micode.notes.gtask.data.TaskList;
|
||||
import net.micode.notes.gtask.exception.ActionFailureException;
|
||||
import net.micode.notes.gtask.exception.NetworkFailureException;
|
||||
import net.micode.notes.tool.DataUtils;
|
||||
import net.micode.notes.tool.GTaskStringUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class GTaskManager {
|
||||
private static final String TAG = GTaskManager.class.getSimpleName();
|
||||
|
||||
public static final int STATE_SUCCESS = 0;
|
||||
|
||||
public static final int STATE_NETWORK_ERROR = 1;
|
||||
|
||||
public static final int STATE_INTERNAL_ERROR = 2;
|
||||
|
||||
public static final int STATE_SYNC_IN_PROGRESS = 3;
|
||||
|
||||
public static final int STATE_SYNC_CANCELLED = 4;
|
||||
|
||||
private static GTaskManager mInstance = null;
|
||||
|
||||
private Activity mActivity;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private ContentResolver mContentResolver;
|
||||
|
||||
private boolean mSyncing;
|
||||
|
||||
private boolean mCancelled;
|
||||
|
||||
private HashMap<String, TaskList> mGTaskListHashMap;
|
||||
|
||||
private HashMap<String, Node> mGTaskHashMap;
|
||||
|
||||
private HashMap<String, MetaData> mMetaHashMap;
|
||||
|
||||
private TaskList mMetaList;
|
||||
|
||||
private HashSet<Long> mLocalDeleteIdMap;
|
||||
|
||||
private HashMap<String, Long> mGidToNid;
|
||||
|
||||
private HashMap<Long, String> mNidToGid;
|
||||
|
||||
private GTaskManager() {
|
||||
mSyncing = false;
|
||||
mCancelled = false;
|
||||
mGTaskListHashMap = new HashMap<String, TaskList>();
|
||||
mGTaskHashMap = new HashMap<String, Node>();
|
||||
mMetaHashMap = new HashMap<String, MetaData>();
|
||||
mMetaList = null;
|
||||
mLocalDeleteIdMap = new HashSet<Long>();
|
||||
mGidToNid = new HashMap<String, Long>();
|
||||
mNidToGid = new HashMap<Long, String>();
|
||||
}
|
||||
|
||||
public static synchronized GTaskManager getInstance() {
|
||||
if (mInstance == null) {
|
||||
mInstance = new GTaskManager();
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public synchronized void setActivityContext(Activity activity) {
|
||||
// used for getting authtoken
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
public int sync(Context context, GTaskASyncTask asyncTask) {
|
||||
if (mSyncing) {
|
||||
Log.d(TAG, "Sync is in progress");
|
||||
return STATE_SYNC_IN_PROGRESS;
|
||||
}
|
||||
mContext = context;
|
||||
mContentResolver = mContext.getContentResolver();
|
||||
mSyncing = true;
|
||||
mCancelled = false;
|
||||
mGTaskListHashMap.clear();
|
||||
mGTaskHashMap.clear();
|
||||
mMetaHashMap.clear();
|
||||
mLocalDeleteIdMap.clear();
|
||||
mGidToNid.clear();
|
||||
mNidToGid.clear();
|
||||
|
||||
try {
|
||||
GTaskClient client = GTaskClient.getInstance();
|
||||
client.resetUpdateArray();
|
||||
|
||||
// login google task
|
||||
if (!mCancelled) {
|
||||
if (!client.login(mActivity)) {
|
||||
throw new NetworkFailureException("login google task failed");
|
||||
}
|
||||
}
|
||||
|
||||
// get the task list from google
|
||||
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
|
||||
initGTaskList();
|
||||
|
||||
// do content sync work
|
||||
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
|
||||
syncContent();
|
||||
} catch (NetworkFailureException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
return STATE_NETWORK_ERROR;
|
||||
} catch (ActionFailureException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
return STATE_INTERNAL_ERROR;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.toString());
|
||||
e.printStackTrace();
|
||||
return STATE_INTERNAL_ERROR;
|
||||
} finally {
|
||||
mGTaskListHashMap.clear();
|
||||
mGTaskHashMap.clear();
|
||||
mMetaHashMap.clear();
|
||||
mLocalDeleteIdMap.clear();
|
||||
mGidToNid.clear();
|
||||
mNidToGid.clear();
|
||||
mSyncing = false;
|
||||
}
|
||||
|
||||
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
|
||||
}
|
||||
|
||||
private void initGTaskList() throws NetworkFailureException {
|
||||
if (mCancelled)
|
||||
return;
|
||||
GTaskClient client = GTaskClient.getInstance();
|
||||
try {
|
||||
JSONArray jsTaskLists = client.getTaskLists();
|
||||
|
||||
// init meta list first
|
||||
mMetaList = null;
|
||||
for (int i = 0; i < jsTaskLists.length(); i++) {
|
||||
JSONObject object = jsTaskLists.getJSONObject(i);
|
||||
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
|
||||
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
|
||||
|
||||
if (name
|
||||
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
|
||||
mMetaList = new TaskList();
|
||||
mMetaList.setContentByRemoteJSON(object);
|
||||
|
||||
// load meta data
|
||||
JSONArray jsMetas = client.getTaskList(gid);
|
||||
for (int j = 0; j < jsMetas.length(); j++) {
|
||||
object = (JSONObject) jsMetas.getJSONObject(j);
|
||||
MetaData metaData = new MetaData();
|
||||
metaData.setContentByRemoteJSON(object);
|
||||
if (metaData.isWorthSaving()) {
|
||||
mMetaList.addChildTask(metaData);
|
||||
if (metaData.getGid() != null) {
|
||||
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create meta list if not existed
|
||||
if (mMetaList == null) {
|
||||
mMetaList = new TaskList();
|
||||
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
|
||||
+ GTaskStringUtils.FOLDER_META);
|
||||
GTaskClient.getInstance().createTaskList(mMetaList);
|
||||
}
|
||||
|
||||
// init task list
|
||||
for (int i = 0; i < jsTaskLists.length(); i++) {
|
||||
JSONObject object = jsTaskLists.getJSONObject(i);
|
||||
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
|
||||
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
|
||||
|
||||
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
|
||||
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
|
||||
+ GTaskStringUtils.FOLDER_META)) {
|
||||
TaskList tasklist = new TaskList();
|
||||
tasklist.setContentByRemoteJSON(object);
|
||||
mGTaskListHashMap.put(gid, tasklist);
|
||||
mGTaskHashMap.put(gid, tasklist);
|
||||
|
||||
// load tasks
|
||||
JSONArray jsTasks = client.getTaskList(gid);
|
||||
for (int j = 0; j < jsTasks.length(); j++) {
|
||||
object = (JSONObject) jsTasks.getJSONObject(j);
|
||||
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
|
||||
Task task = new Task();
|
||||
task.setContentByRemoteJSON(object);
|
||||
if (task.isWorthSaving()) {
|
||||
task.setMetaInfo(mMetaHashMap.get(gid));
|
||||
tasklist.addChildTask(task);
|
||||
mGTaskHashMap.put(gid, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
e.printStackTrace();
|
||||
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void syncContent() throws NetworkFailureException {
|
||||
int syncType;
|
||||
Cursor c = null;
|
||||
String gid;
|
||||
Node node;
|
||||
|
||||
mLocalDeleteIdMap.clear();
|
||||
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// for local deleted note
|
||||
try {
|
||||
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
|
||||
"(type<>? AND parent_id=?)", new String[] {
|
||||
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_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;
|
||||
}
|
||||
}
|
||||
|
||||
// sync folder first
|
||||
syncFolder();
|
||||
|
||||
// for note existing in database
|
||||
try {
|
||||
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
|
||||
"(type=? AND parent_id<>?)", new String[] {
|
||||
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_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) {
|
||||
// local add
|
||||
syncType = Node.SYNC_ACTION_ADD_REMOTE;
|
||||
} else {
|
||||
// remote delete
|
||||
syncType = Node.SYNC_ACTION_DEL_LOCAL;
|
||||
}
|
||||
}
|
||||
doContentSync(syncType, node, c);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "failed to query existing note in database");
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
c = null;
|
||||
}
|
||||
}
|
||||
|
||||
// go through remaining items
|
||||
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<String, Node> entry = iter.next();
|
||||
node = entry.getValue();
|
||||
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
|
||||
}
|
||||
|
||||
// mCancelled can be set by another thread, so we neet to check one by
|
||||
// one
|
||||
// clear local delete table
|
||||
if (!mCancelled) {
|
||||
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
|
||||
throw new ActionFailureException("failed to batch-delete local deleted notes");
|
||||
}
|
||||
}
|
||||
|
||||
// refresh local sync id
|
||||
if (!mCancelled) {
|
||||
GTaskClient.getInstance().commitUpdate();
|
||||
refreshLocalSyncId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void syncFolder() throws NetworkFailureException {
|
||||
Cursor c = null;
|
||||
String gid;
|
||||
Node node;
|
||||
int syncType;
|
||||
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// for root folder
|
||||
try {
|
||||
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
|
||||
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
|
||||
if (c != null) {
|
||||
c.moveToNext();
|
||||
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
|
||||
node = mGTaskHashMap.get(gid);
|
||||
if (node != null) {
|
||||
mGTaskHashMap.remove(gid);
|
||||
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
|
||||
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
|
||||
// for system folder, only update remote name if necessary
|
||||
if (!node.getName().equals(
|
||||
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
|
||||
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
|
||||
} else {
|
||||
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "failed to query root folder");
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
c = null;
|
||||
}
|
||||
}
|
||||
|
||||
// for call-note folder
|
||||
try {
|
||||
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
|
||||
new String[] {
|
||||
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
|
||||
}, null);
|
||||
if (c != null) {
|
||||
if (c.moveToNext()) {
|
||||
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
|
||||
node = mGTaskHashMap.get(gid);
|
||||
if (node != null) {
|
||||
mGTaskHashMap.remove(gid);
|
||||
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
|
||||
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
|
||||
// for system folder, only update remote name if
|
||||
// necessary
|
||||
if (!node.getName().equals(
|
||||
GTaskStringUtils.MIUI_FOLDER_PREFFIX
|
||||
+ GTaskStringUtils.FOLDER_CALL_NOTE))
|
||||
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
|
||||
} else {
|
||||
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "failed to query call note folder");
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
c = null;
|
||||
}
|
||||
}
|
||||
|
||||
// for local existing folders
|
||||
try {
|
||||
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
|
||||
"(type=? AND parent_id<>?)", new String[] {
|
||||
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_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) {
|
||||
// local add
|
||||
syncType = Node.SYNC_ACTION_ADD_REMOTE;
|
||||
} else {
|
||||
// remote delete
|
||||
syncType = Node.SYNC_ACTION_DEL_LOCAL;
|
||||
}
|
||||
}
|
||||
doContentSync(syncType, node, c);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "failed to query existing folder");
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
c = null;
|
||||
}
|
||||
}
|
||||
|
||||
// for remote add folders
|
||||
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<String, TaskList> entry = iter.next();
|
||||
gid = entry.getKey();
|
||||
node = entry.getValue();
|
||||
if (mGTaskHashMap.containsKey(gid)) {
|
||||
mGTaskHashMap.remove(gid);
|
||||
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mCancelled)
|
||||
GTaskClient.getInstance().commitUpdate();
|
||||
}
|
||||
|
||||
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
MetaData meta;
|
||||
switch (syncType) {
|
||||
case Node.SYNC_ACTION_ADD_LOCAL:
|
||||
addLocalNode(node);
|
||||
break;
|
||||
case Node.SYNC_ACTION_ADD_REMOTE:
|
||||
addRemoteNode(node, c);
|
||||
break;
|
||||
case Node.SYNC_ACTION_DEL_LOCAL:
|
||||
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
|
||||
if (meta != null) {
|
||||
GTaskClient.getInstance().deleteNode(meta);
|
||||
}
|
||||
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
|
||||
break;
|
||||
case Node.SYNC_ACTION_DEL_REMOTE:
|
||||
meta = mMetaHashMap.get(node.getGid());
|
||||
if (meta != null) {
|
||||
GTaskClient.getInstance().deleteNode(meta);
|
||||
}
|
||||
GTaskClient.getInstance().deleteNode(node);
|
||||
break;
|
||||
case Node.SYNC_ACTION_UPDATE_LOCAL:
|
||||
updateLocalNode(node, c);
|
||||
break;
|
||||
case Node.SYNC_ACTION_UPDATE_REMOTE:
|
||||
updateRemoteNode(node, c);
|
||||
break;
|
||||
case Node.SYNC_ACTION_UPDATE_CONFLICT:
|
||||
// merging both modifications maybe a good idea
|
||||
// right now just use local update simply
|
||||
updateRemoteNode(node, c);
|
||||
break;
|
||||
case Node.SYNC_ACTION_NONE:
|
||||
break;
|
||||
case Node.SYNC_ACTION_ERROR:
|
||||
default:
|
||||
throw new ActionFailureException("unkown sync action type");
|
||||
}
|
||||
}
|
||||
|
||||
private void addLocalNode(Node node) throws NetworkFailureException {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
SqlNote sqlNote;
|
||||
if (node instanceof TaskList) {
|
||||
if (node.getName().equals(
|
||||
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
|
||||
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
|
||||
} else if (node.getName().equals(
|
||||
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
|
||||
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
|
||||
} else {
|
||||
sqlNote = new SqlNote(mContext);
|
||||
sqlNote.setContent(node.getLocalJSONFromContent());
|
||||
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
|
||||
}
|
||||
} else {
|
||||
sqlNote = new SqlNote(mContext);
|
||||
JSONObject js = node.getLocalJSONFromContent();
|
||||
try {
|
||||
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
|
||||
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
|
||||
if (note.has(NoteColumns.ID)) {
|
||||
long id = note.getLong(NoteColumns.ID);
|
||||
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
|
||||
// the id is not available, have to create a new one
|
||||
note.remove(NoteColumns.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
|
||||
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
|
||||
for (int i = 0; i < dataArray.length(); i++) {
|
||||
JSONObject data = dataArray.getJSONObject(i);
|
||||
if (data.has(DataColumns.ID)) {
|
||||
long dataId = data.getLong(DataColumns.ID);
|
||||
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
|
||||
// the data id is not available, have to create
|
||||
// a new one
|
||||
data.remove(DataColumns.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.w(TAG, e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
sqlNote.setContent(js);
|
||||
|
||||
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
|
||||
if (parentId == null) {
|
||||
Log.e(TAG, "cannot find task's parent id locally");
|
||||
throw new ActionFailureException("cannot add local node");
|
||||
}
|
||||
sqlNote.setParentId(parentId.longValue());
|
||||
}
|
||||
|
||||
// create the local node
|
||||
sqlNote.setGtaskId(node.getGid());
|
||||
sqlNote.commit(false);
|
||||
|
||||
// update gid-nid mapping
|
||||
mGidToNid.put(node.getGid(), sqlNote.getId());
|
||||
mNidToGid.put(sqlNote.getId(), node.getGid());
|
||||
|
||||
// update meta
|
||||
updateRemoteMeta(node.getGid(), sqlNote);
|
||||
}
|
||||
|
||||
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
SqlNote sqlNote;
|
||||
// update the note locally
|
||||
sqlNote = new SqlNote(mContext, c);
|
||||
sqlNote.setContent(node.getLocalJSONFromContent());
|
||||
|
||||
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
|
||||
: new Long(Notes.ID_ROOT_FOLDER);
|
||||
if (parentId == null) {
|
||||
Log.e(TAG, "cannot find task's parent id locally");
|
||||
throw new ActionFailureException("cannot update local node");
|
||||
}
|
||||
sqlNote.setParentId(parentId.longValue());
|
||||
sqlNote.commit(true);
|
||||
|
||||
// update meta info
|
||||
updateRemoteMeta(node.getGid(), sqlNote);
|
||||
}
|
||||
|
||||
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
SqlNote sqlNote = new SqlNote(mContext, c);
|
||||
Node n;
|
||||
|
||||
// update remotely
|
||||
if (sqlNote.isNoteType()) {
|
||||
Task task = new Task();
|
||||
task.setContentByLocalJSON(sqlNote.getContent());
|
||||
|
||||
String parentGid = mNidToGid.get(sqlNote.getParentId());
|
||||
if (parentGid == null) {
|
||||
Log.e(TAG, "cannot find task's parent tasklist");
|
||||
throw new ActionFailureException("cannot add remote task");
|
||||
}
|
||||
mGTaskListHashMap.get(parentGid).addChildTask(task);
|
||||
|
||||
GTaskClient.getInstance().createTask(task);
|
||||
n = (Node) task;
|
||||
|
||||
// add meta
|
||||
updateRemoteMeta(task.getGid(), sqlNote);
|
||||
} else {
|
||||
TaskList tasklist = null;
|
||||
|
||||
// we need to skip folder if it has already existed
|
||||
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
|
||||
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
|
||||
folderName += GTaskStringUtils.FOLDER_DEFAULT;
|
||||
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
|
||||
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
|
||||
else
|
||||
folderName += sqlNote.getSnippet();
|
||||
|
||||
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<String, TaskList> entry = iter.next();
|
||||
String gid = entry.getKey();
|
||||
TaskList list = entry.getValue();
|
||||
|
||||
if (list.getName().equals(folderName)) {
|
||||
tasklist = list;
|
||||
if (mGTaskHashMap.containsKey(gid)) {
|
||||
mGTaskHashMap.remove(gid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no match we can add now
|
||||
if (tasklist == null) {
|
||||
tasklist = new TaskList();
|
||||
tasklist.setContentByLocalJSON(sqlNote.getContent());
|
||||
GTaskClient.getInstance().createTaskList(tasklist);
|
||||
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
|
||||
}
|
||||
n = (Node) tasklist;
|
||||
}
|
||||
|
||||
// update local note
|
||||
sqlNote.setGtaskId(n.getGid());
|
||||
sqlNote.commit(false);
|
||||
sqlNote.resetLocalModified();
|
||||
sqlNote.commit(true);
|
||||
|
||||
// gid-id mapping
|
||||
mGidToNid.put(n.getGid(), sqlNote.getId());
|
||||
mNidToGid.put(sqlNote.getId(), n.getGid());
|
||||
}
|
||||
|
||||
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
SqlNote sqlNote = new SqlNote(mContext, c);
|
||||
|
||||
// update remotely
|
||||
node.setContentByLocalJSON(sqlNote.getContent());
|
||||
GTaskClient.getInstance().addUpdateNode(node);
|
||||
|
||||
// update meta
|
||||
updateRemoteMeta(node.getGid(), sqlNote);
|
||||
|
||||
// move task if necessary
|
||||
if (sqlNote.isNoteType()) {
|
||||
Task task = (Task) node;
|
||||
TaskList preParentList = task.getParent();
|
||||
|
||||
String curParentGid = mNidToGid.get(sqlNote.getParentId());
|
||||
if (curParentGid == null) {
|
||||
Log.e(TAG, "cannot find task's parent tasklist");
|
||||
throw new ActionFailureException("cannot update remote task");
|
||||
}
|
||||
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
|
||||
|
||||
if (preParentList != curParentList) {
|
||||
preParentList.removeChildTask(task);
|
||||
curParentList.addChildTask(task);
|
||||
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
|
||||
}
|
||||
}
|
||||
|
||||
// clear local modified flag
|
||||
sqlNote.resetLocalModified();
|
||||
sqlNote.commit(true);
|
||||
}
|
||||
|
||||
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
|
||||
if (sqlNote != null && sqlNote.isNoteType()) {
|
||||
MetaData metaData = mMetaHashMap.get(gid);
|
||||
if (metaData != null) {
|
||||
metaData.setMeta(gid, sqlNote.getContent());
|
||||
GTaskClient.getInstance().addUpdateNode(metaData);
|
||||
} else {
|
||||
metaData = new MetaData();
|
||||
metaData.setMeta(gid, sqlNote.getContent());
|
||||
mMetaList.addChildTask(metaData);
|
||||
mMetaHashMap.put(gid, metaData);
|
||||
GTaskClient.getInstance().createTask(metaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshLocalSyncId() throws NetworkFailureException {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the latest gtask list
|
||||
mGTaskHashMap.clear();
|
||||
mGTaskListHashMap.clear();
|
||||
mMetaHashMap.clear();
|
||||
initGTaskList();
|
||||
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
|
||||
"(type<>? AND parent_id<>?)", new String[] {
|
||||
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
|
||||
}, NoteColumns.TYPE + " DESC");
|
||||
if (c != null) {
|
||||
while (c.moveToNext()) {
|
||||
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
|
||||
Node node = mGTaskHashMap.get(gid);
|
||||
if (node != null) {
|
||||
mGTaskHashMap.remove(gid);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(NoteColumns.SYNC_ID, node.getLastModified());
|
||||
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
|
||||
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
|
||||
} else {
|
||||
Log.e(TAG, "something is missed");
|
||||
throw new ActionFailureException(
|
||||
"some local items don't have gid after sync");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "failed to query local note to refresh sync id");
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
c = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getSyncAccount() {
|
||||
return GTaskClient.getInstance().getSyncAccount().name;
|
||||
}
|
||||
|
||||
public void cancelSync() {
|
||||
mCancelled = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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对象
|
||||
private Note mNote;
|
||||
// 笔记ID
|
||||
private long mNoteId;
|
||||
// 笔记内容
|
||||
private String mContent;
|
||||
// 笔记模式
|
||||
private int mMode;
|
||||
|
||||
// 笔记提醒日期
|
||||
private long mAlertDate;
|
||||
|
||||
// 笔记修改日期
|
||||
private long mModifiedDate;
|
||||
|
||||
// 笔记背景颜色ID
|
||||
private int mBgColorId;
|
||||
|
||||
// 笔记小部件ID
|
||||
private int mWidgetId;
|
||||
|
||||
// 笔记小部件类型
|
||||
private int mWidgetType;
|
||||
|
||||
// 笔记所属文件夹ID
|
||||
private long mFolderId;
|
||||
|
||||
// 上下文对象
|
||||
private Context mContext;
|
||||
|
||||
// 日志标签
|
||||
private static final String TAG = "WorkingNote";
|
||||
|
||||
// 标记笔记是否已删除
|
||||
private boolean mIsDeleted;
|
||||
|
||||
// 笔记设置变化监听器
|
||||
private NoteSettingChangedListener mNoteSettingStatusListener;
|
||||
|
||||
// 数据查询时使用的字段数组
|
||||
public static final String[] DATA_PROJECTION = new String[] {
|
||||
DataColumns.ID,
|
||||
DataColumns.CONTENT,
|
||||
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
|
||||
};
|
||||
|
||||
// 数据ID字段在查询结果中的索引
|
||||
private static final int DATA_ID_COLUMN = 0;
|
||||
|
||||
// 数据内容字段在查询结果中的索引
|
||||
private static final int DATA_CONTENT_COLUMN = 1;
|
||||
|
||||
// 数据MIME类型字段在查询结果中的索引
|
||||
private static final int DATA_MIME_TYPE_COLUMN = 2;
|
||||
|
||||
// 数据模式字段在查询结果中的索引
|
||||
private static final int DATA_MODE_COLUMN = 3;
|
||||
|
||||
// 笔记父ID字段在查询结果中的索引
|
||||
private static final int NOTE_PARENT_ID_COLUMN = 0;
|
||||
|
||||
// 笔记提醒日期字段在查询结果中的索引
|
||||
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
|
||||
|
||||
// 笔记背景颜色ID字段在查询结果中的索引
|
||||
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
|
||||
|
||||
// 笔记小部件ID字段在查询结果中的索引
|
||||
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;
|
||||
|
||||
// 创建新笔记的构造函数
|
||||
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;
|
||||
}
|
||||
|
||||
// 加载现有笔记的构造函数
|
||||
private WorkingNote(Context context, long noteId, long folderId) {
|
||||
mContext = context;
|
||||
mNoteId = noteId;
|
||||
mFolderId = folderId;
|
||||
mIsDeleted = false;
|
||||
mNote = new Note();
|
||||
loadNote();
|
||||
}
|
||||
|
||||
// 加载笔记信息的方法
|
||||
private void loadNote() {
|
||||
// 从数据库加载笔记信息的代码...
|
||||
}
|
||||
|
||||
// 加载笔记数据的方法
|
||||
private void loadNoteData() {
|
||||
// 从数据库加载笔记数据的代码...
|
||||
}
|
||||
|
||||
// 创建空笔记的静态方法
|
||||
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
|
||||
int widgetType, int defaultBgColorId) {
|
||||
// 创建并返回一个空的WorkingNote对象的代码...
|
||||
}
|
||||
|
||||
// 加载笔记的静态方法
|
||||
public static WorkingNote load(Context context, long id) {
|
||||
return new WorkingNote(context, id, 0);
|
||||
}
|
||||
|
||||
// 保存笔记的同步方法
|
||||
public synchronized boolean saveNote() {
|
||||
// 保存笔记的代码...
|
||||
}
|
||||
|
||||
// 检查笔记是否存在于数据库中的方法
|
||||
public boolean existInDatabase() {
|
||||
return mNoteId > 0;
|
||||
}
|
||||
|
||||
// 检查笔记是否值得保存的方法
|
||||
private boolean isWorthSaving() {
|
||||
// 判断笔记是否值得保存的代码...
|
||||
}
|
||||
|
||||
// 设置笔记设置变化监听器的方法
|
||||
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
|
||||
mNoteSettingStatusListener = l;
|
||||
}
|
||||
|
||||
// 设置提醒日期的方法
|
||||
public void setAlertDate(long date, boolean set) {
|
||||
// 设置提醒日期的代码...
|
||||
}
|
||||
|
||||
// 标记笔记为删除的方法
|
||||
public void markDeleted(boolean mark) {
|
||||
// 标记笔记为删除的代码...
|
||||
}
|
||||
|
||||
// 设置背景颜色ID的方法
|
||||
public void setBgColorId(int id) {
|
||||
// 设置背景颜色ID的代码...
|
||||
}
|
||||
|
||||
// 设置清单模式的方法
|
||||
public void setCheckListMode(int mode) {
|
||||
// 设置清单模式的代码...
|
||||
}
|
||||
|
||||
// 设置小部件类型的方法
|
||||
public void setWidgetType(int type) {
|
||||
// 设置小部件类型的代码...
|
||||
}
|
||||
|
||||
// 设置小部件ID的方法
|
||||
public void setWidgetId(int id) {
|
||||
// 设置小部件ID的代码...
|
||||
}
|
||||
|
||||
// 设置工作笔记文本的方法
|
||||
public void setWorkingText(String text) {
|
||||
// 设置工作笔记文本的代码...
|
||||
}
|
||||
|
||||
// 转换为通话笔记的方法
|
||||
public void convertToCallNote(String phoneNumber, long callDate) {
|
||||
// 转换为通话笔记的代码...
|
||||
}
|
||||
|
||||
// 检查笔记是否有时钟提醒的方法
|
||||
public boolean hasClockAlert() {
|
||||
return (mAlertDate > 0 ? true : false);
|
||||
}
|
||||
|
||||
// 获取笔记内容的方法
|
||||
public String getContent() {
|
||||
return mContent;
|
||||
}
|
||||
|
||||
// 获取提醒日期的方法
|
||||
public long getAlertDate() {
|
||||
return mAlertDate;
|
||||
}
|
||||
|
||||
// 获取修改日期的方法
|
||||
public long getModifiedDate() {
|
||||
return mModifiedDate;
|
||||
}
|
||||
|
||||
// 获取笔记背景颜色资源ID的方法
|
||||
public int getBgColorResId() {
|
||||
return NoteBgResources.getNoteBgResource(mBgColorId);
|
||||
}
|
||||
|
||||
// 获取笔记背景颜色ID的方法
|
||||
public int getBgColorId() {
|
||||
return mBgColorId;
|
||||
}
|
||||
|
||||
// 获取笔记标题背景资源ID的方法
|
||||
public int getTitleBgResId() {
|
||||
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
|
||||
}
|
||||
|
||||
// 获取清单模式的方法
|
||||
public int getCheckListMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
// 获取笔记ID的方法
|
||||
public long getNoteId() {
|
||||
return mNoteId;
|
||||
}
|
||||
|
||||
// 获取文件夹ID的方法
|
||||
public long getFolderId() {
|
||||
return mFolderId;
|
||||
}
|
||||
|
||||
// 获取小部件ID的方法
|
||||
public int getWidgetId() {
|
||||
return mWidgetId;
|
||||
}
|
||||
|
||||
// 获取小部件类型的方法
|
||||
public int getWidgetType() {
|
||||
return mWidgetType;
|
||||
}
|
||||
|
||||
// 笔记设置变化监听器接口
|
||||
public interface NoteSettingChangedListener {
|
||||
// 当前笔记背景颜色刚刚更改时调用
|
||||
void onBackgroundColorChanged();
|
||||
|
||||
// 用户设置时钟时调用
|
||||
void onClockAlertChanged(long date, boolean set);
|
||||
|
||||
// 用户从widget创建笔记时调用
|
||||
void onWidgetChanged();
|
||||
|
||||
// 用户在清单模式和普通模式之间切换时调用
|
||||
// @param oldMode 是更改前的模式
|
||||
// @param newMode 是新模式
|
||||
void onCheckListModeChanged(int oldMode, int newMode);
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.tool;
|
||||
|
||||
public class GTaskStringUtils {
|
||||
|
||||
// 表示Google任务中的动作ID
|
||||
public final static String GTASK_JSON_ACTION_ID = "action_id";
|
||||
// 表示动作列表
|
||||
public final static String GTASK_JSON_ACTION_LIST = "action_list";
|
||||
// 表示动作类型
|
||||
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
|
||||
// 创建动作类型
|
||||
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
|
||||
// 获取所有动作类型
|
||||
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
|
||||
// 移动动作类型
|
||||
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
|
||||
// 更新动作类型
|
||||
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
|
||||
// 创建者ID
|
||||
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
|
||||
// 子实体
|
||||
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
|
||||
// 客户端版本
|
||||
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
|
||||
// 表示任务是否完成
|
||||
public final static String GTASK_JSON_COMPLETED = "completed";
|
||||
// 当前列表ID
|
||||
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
|
||||
// 默认列表ID
|
||||
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
|
||||
// 表示任务是否被删除
|
||||
public final static String GTASK_JSON_DELETED = "deleted";
|
||||
// 目标列表
|
||||
public final static String GTASK_JSON_DEST_LIST = "dest_list";
|
||||
// 目标父实体
|
||||
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
|
||||
// 目标父实体类型
|
||||
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
|
||||
// 实体变化
|
||||
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
|
||||
// 实体类型
|
||||
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
|
||||
// 获取已删除的任务
|
||||
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
|
||||
// ID
|
||||
public final static String GTASK_JSON_ID = "id";
|
||||
// 索引
|
||||
public final static String GTASK_JSON_INDEX = "index";
|
||||
// 最后修改时间
|
||||
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
|
||||
// 最新的同步点
|
||||
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
|
||||
// 列表ID
|
||||
public final static String GTASK_JSON_LIST_ID = "list_id";
|
||||
// 列表集合
|
||||
public final static String GTASK_JSON_LISTS = "lists";
|
||||
// 名称
|
||||
public final static String GTASK_JSON_NAME = "name";
|
||||
// 新ID
|
||||
public final static String GTASK_JSON_NEW_ID = "new_id";
|
||||
// 笔记
|
||||
public final static String GTASK_JSON_NOTES = "notes";
|
||||
// 父ID
|
||||
public final static String GTASK_JSON_PARENT_ID = "parent_id";
|
||||
// 之前的兄弟ID
|
||||
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
|
||||
// 结果
|
||||
public final static String GTASK_JSON_RESULTS = "results";
|
||||
// 源列表
|
||||
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
|
||||
// 任务集合
|
||||
public final static String GTASK_JSON_TASKS = "tasks";
|
||||
// 类型
|
||||
public final static String GTASK_JSON_TYPE = "type";
|
||||
// 分组类型
|
||||
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
|
||||
// 任务类型
|
||||
public final static String GTASK_JSON_TYPE_TASK = "TASK";
|
||||
// 用户
|
||||
public final static String GTASK_JSON_USER = "user";
|
||||
|
||||
// 小米文件夹前缀
|
||||
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
|
||||
// 默认文件夹名称
|
||||
public final static String FOLDER_DEFAULT = "Default";
|
||||
// 通话笔记文件夹名称
|
||||
public final static String FOLDER_CALL_NOTE = "Call_Note";
|
||||
// 元数据文件夹名称
|
||||
public final static String FOLDER_META = "METADATA";
|
||||
|
||||
// 元数据中Google任务ID的键
|
||||
public final static String META_HEAD_GTASK_ID = "meta_gid";
|
||||
// 元数据中笔记的键
|
||||
public final static String META_HEAD_NOTE = "meta_note";
|
||||
// 元数据中数据的键
|
||||
public final static String META_HEAD_DATA = "meta_data";
|
||||
// 元数据笔记的名称
|
||||
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class AlarmReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
intent.setClass(context, AlarmAlertActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import net.micode.notes.data.Contact;
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.data.Notes.NoteColumns;
|
||||
import net.micode.notes.tool.DataUtils;
|
||||
|
||||
|
||||
public class NoteItemData {
|
||||
static final String [] PROJECTION = new String [] {
|
||||
NoteColumns.ID,
|
||||
NoteColumns.ALERTED_DATE,
|
||||
NoteColumns.BG_COLOR_ID,
|
||||
NoteColumns.CREATED_DATE,
|
||||
NoteColumns.HAS_ATTACHMENT,
|
||||
NoteColumns.MODIFIED_DATE,
|
||||
NoteColumns.NOTES_COUNT,
|
||||
NoteColumns.PARENT_ID,
|
||||
NoteColumns.SNIPPET,
|
||||
NoteColumns.TYPE,
|
||||
NoteColumns.WIDGET_ID,
|
||||
NoteColumns.WIDGET_TYPE,
|
||||
};
|
||||
|
||||
private static final int ID_COLUMN = 0;
|
||||
private static final int ALERTED_DATE_COLUMN = 1;
|
||||
private static final int BG_COLOR_ID_COLUMN = 2;
|
||||
private static final int CREATED_DATE_COLUMN = 3;
|
||||
private static final int HAS_ATTACHMENT_COLUMN = 4;
|
||||
private static final int MODIFIED_DATE_COLUMN = 5;
|
||||
private static final int NOTES_COUNT_COLUMN = 6;
|
||||
private static final int PARENT_ID_COLUMN = 7;
|
||||
private static final int SNIPPET_COLUMN = 8;
|
||||
private static final int TYPE_COLUMN = 9;
|
||||
private static final int WIDGET_ID_COLUMN = 10;
|
||||
private static final int WIDGET_TYPE_COLUMN = 11;
|
||||
|
||||
private long mId;
|
||||
private long mAlertDate;
|
||||
private int mBgColorId;
|
||||
private long mCreatedDate;
|
||||
private boolean mHasAttachment;
|
||||
private long mModifiedDate;
|
||||
private int mNotesCount;
|
||||
private long mParentId;
|
||||
private String mSnippet;
|
||||
private int mType;
|
||||
private int mWidgetId;
|
||||
private int mWidgetType;
|
||||
private String mName;
|
||||
private String mPhoneNumber;
|
||||
|
||||
private boolean mIsLastItem;
|
||||
private boolean mIsFirstItem;
|
||||
private boolean mIsOnlyOneItem;
|
||||
private boolean mIsOneNoteFollowingFolder;
|
||||
private boolean mIsMultiNotesFollowingFolder;
|
||||
|
||||
public NoteItemData(Context context, Cursor cursor) {
|
||||
// 从游标中提取笔记项的数据
|
||||
mId = cursor.getLong(ID_COLUMN); // 笔记项的ID
|
||||
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); // 闹钟提醒日期
|
||||
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); // 背景颜色ID
|
||||
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); // 父文件夹ID
|
||||
mSnippet = cursor.getString(SNIPPET_COLUMN); // 笔记片段
|
||||
// 移除TAG_CHECKED和TAG_UNCHECKED标签
|
||||
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
|
||||
NoteEditActivity.TAG_UNCHECKED, "");
|
||||
mType = cursor.getInt(TYPE_COLUMN); // 笔记类型
|
||||
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); // 小部件ID
|
||||
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); // 小部件类型
|
||||
|
||||
mPhoneNumber = ""; // 电话号初始为空
|
||||
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
|
||||
// 如果笔记项是呼叫记录文件夹类型,则获取电话号码
|
||||
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
|
||||
if (!TextUtils.isEmpty(mPhoneNumber)) {
|
||||
// 如果电话号码不为空,则尝试获取联系人名称
|
||||
mName = Contact.getContact(context, mPhoneNumber);
|
||||
if (mName == null) {
|
||||
mName = mPhoneNumber; // 如果联系人名称为空,则使用电话号码
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mName == null) {
|
||||
mName = ""; // 如果没有获取到名称,则名称字段设为空字符串
|
||||
}
|
||||
checkPostion(cursor); // 检查位置信息
|
||||
}
|
||||
|
||||
private void checkPostion(Cursor cursor) {
|
||||
// 检查笔记项在游标中的位置信息
|
||||
mIsLastItem = cursor.isLast() ? true : false; // 是否是最后一个项
|
||||
mIsFirstItem = cursor.isFirst() ? true : false; // 是否是第一个项
|
||||
mIsOnlyOneItem = (cursor.getCount() == 1); // 是否仅有一个项
|
||||
|
||||
// 检查是否有多条笔记或单个笔记在文件夹后
|
||||
mIsMultiNotesFollowingFolder = false;
|
||||
mIsOneNoteFollowingFolder = false;
|
||||
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
|
||||
int position = cursor.getPosition(); // 当前游标位置
|
||||
if (cursor.moveToPrevious()) { // 移动到上一个项
|
||||
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|
||||
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
|
||||
if (cursor.getCount() > (position + 1)) {
|
||||
mIsMultiNotesFollowingFolder = true; // 有多个笔记跟随文件夹
|
||||
} else {
|
||||
mIsOneNoteFollowingFolder = true; // 有一个笔记跟随文件夹
|
||||
}
|
||||
}
|
||||
if (!cursor.moveToNext()) { // 移动回原来的位置
|
||||
throw new IllegalStateException("游标可以向前移动,但不能向后移动");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public boolean isOneFollowingFolder() {
|
||||
return mIsOneNoteFollowingFolder;
|
||||
}
|
||||
|
||||
public boolean isMultiFollowingFolder() {
|
||||
return mIsMultiNotesFollowingFolder;
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return mIsLastItem;
|
||||
}
|
||||
|
||||
public String getCallName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public boolean isFirst() {
|
||||
return mIsFirstItem;
|
||||
}
|
||||
|
||||
public boolean isSingle() {
|
||||
return mIsOnlyOneItem;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public long getAlertDate() {
|
||||
return mAlertDate;
|
||||
}
|
||||
|
||||
public long getCreatedDate() {
|
||||
return mCreatedDate;
|
||||
}
|
||||
|
||||
public boolean hasAttachment() {
|
||||
return mHasAttachment;
|
||||
}
|
||||
|
||||
public long getModifiedDate() {
|
||||
return mModifiedDate;
|
||||
}
|
||||
|
||||
public int getBgColorId() {
|
||||
return mBgColorId;
|
||||
}
|
||||
|
||||
public long getParentId() {
|
||||
return mParentId;
|
||||
}
|
||||
|
||||
public int getNotesCount() {
|
||||
return mNotesCount;
|
||||
}
|
||||
|
||||
public long getFolderId () {
|
||||
return mParentId;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public int getWidgetType() {
|
||||
return mWidgetType;
|
||||
}
|
||||
|
||||
public int getWidgetId() {
|
||||
return mWidgetId;
|
||||
}
|
||||
|
||||
public String getSnippet() {
|
||||
return mSnippet;
|
||||
}
|
||||
|
||||
public boolean hasAlert() {
|
||||
return (mAlertDate > 0);
|
||||
}
|
||||
|
||||
public boolean isCallRecord() {
|
||||
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
|
||||
}
|
||||
|
||||
public static int getNoteType(Cursor cursor) {
|
||||
return cursor.getInt(TYPE_COLUMN);
|
||||
}
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
|
||||
import net.micode.notes.data.Notes;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
public class NotesListAdapter extends CursorAdapter {
|
||||
private static final String TAG = "NotesListAdapter"; // 类的日志标签
|
||||
private Context mContext; // 上下文对象
|
||||
private HashMap<Integer, Boolean> mSelectedIndex; // 存储每个位置的选择状态
|
||||
private int mNotesCount; // 笔记计数
|
||||
private boolean mChoiceMode; // 是否处于选择模式
|
||||
|
||||
// AppWidgetAttribute 静态内部类,用于存储小部件属性
|
||||
public static class AppWidgetAttribute {
|
||||
public int widgetId; // 小部件ID
|
||||
public int widgetType; // 小部件类型
|
||||
};
|
||||
|
||||
public NotesListAdapter(Context context) {
|
||||
super(context, null); // 调用基类的构造方法
|
||||
mSelectedIndex = new HashMap<Integer, Boolean>(); // 初始化选择状态映射
|
||||
mContext = context; // 初始化上下文
|
||||
mNotesCount = 0; // 初始化笔记计数
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return new NotesListItem(context); // 创建新的列表项视图
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
// 绑定数据到列表项视图
|
||||
if (view instanceof NotesListItem) {
|
||||
NoteItemData itemData = new NoteItemData(context, cursor);
|
||||
((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition()));
|
||||
}
|
||||
}
|
||||
|
||||
// 设置指定位置项的选择状态
|
||||
public void setCheckedItem(final int position, final boolean checked) {
|
||||
mSelectedIndex.put(position, checked); // 更新选择状态
|
||||
notifyDataSetChanged(); // 通知数据发生变化
|
||||
}
|
||||
|
||||
// 判断是否处于选择模式
|
||||
public boolean isInChoiceMode() {
|
||||
return mChoiceMode;
|
||||
}
|
||||
|
||||
// 设置选择模式
|
||||
public void setChoiceMode(boolean mode) {
|
||||
mSelectedIndex.clear(); // 清除选择状态
|
||||
mChoiceMode = mode; // 设置选择模式标志
|
||||
}
|
||||
|
||||
// 全选或全不选
|
||||
public void selectAll(boolean checked) {
|
||||
Cursor cursor = getCursor();
|
||||
for (int i = 0; i < getCount(); i++) {
|
||||
if (cursor.moveToPosition(i)) {
|
||||
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
|
||||
setCheckedItem(i, checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中项的ID集合
|
||||
public HashSet<Long> getSelectedItemIds() {
|
||||
HashSet<Long> itemSet = new HashSet<Long>();
|
||||
for (Integer position : mSelectedIndex.keySet()) {
|
||||
if (mSelectedIndex.get(position) == true) {
|
||||
Long id = getItemId(position);
|
||||
if (id == Notes.ID_ROOT_FOLDER) {
|
||||
Log.d(TAG, "Wrong item id, should not happen");
|
||||
} else {
|
||||
itemSet.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return itemSet;
|
||||
}
|
||||
|
||||
// 获取选中的小部件属性集合
|
||||
public HashSet<AppWidgetAttribute> getSelectedWidget() {
|
||||
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
|
||||
for (Integer position : mSelectedIndex.keySet()) {
|
||||
if (mSelectedIndex.get(position) == true) {
|
||||
Cursor c = (Cursor) getItem(position);
|
||||
if (c != null) {
|
||||
AppWidgetAttribute widget = new AppWidgetAttribute();
|
||||
NoteItemData item = new NoteItemData(mContext, c);
|
||||
widget.widgetId = item.getWidgetId();
|
||||
widget.widgetType = item.getWidgetType();
|
||||
itemSet.add(widget);
|
||||
/**
|
||||
* Don't close cursor here, only the adapter could close it
|
||||
*/
|
||||
} else {
|
||||
Log.e(TAG, "Invalid cursor");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return itemSet;
|
||||
}
|
||||
|
||||
// 获取选中项的数量
|
||||
public int getSelectedCount() {
|
||||
Collection<Boolean> values = mSelectedIndex.values();
|
||||
if (null == values) {
|
||||
return 0;
|
||||
}
|
||||
Iterator<Boolean> iter = values.iterator();
|
||||
int count = 0;
|
||||
while (iter.hasNext()) {
|
||||
if (true == iter.next()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// 判断是否全选
|
||||
public boolean isAllSelected() {
|
||||
int checkedCount = getSelectedCount();
|
||||
return (checkedCount != 0 && checkedCount == mNotesCount);
|
||||
}
|
||||
|
||||
// 判断指定位置项是否被选中
|
||||
public boolean isSelectedItem(final int position) {
|
||||
if (null == mSelectedIndex.get(position)) {
|
||||
return false;
|
||||
}
|
||||
return mSelectedIndex.get(position);
|
||||
}
|
||||
|
||||
// 内容变化时的处理
|
||||
@Override
|
||||
protected void onContentChanged() {
|
||||
super.onContentChanged();
|
||||
calcNotesCount();
|
||||
}
|
||||
|
||||
// 更换游标时的处理
|
||||
@Override
|
||||
public void changeCursor(Cursor cursor) {
|
||||
super.changeCursor(cursor);
|
||||
calcNotesCount();
|
||||
}
|
||||
|
||||
// 计算笔记数量
|
||||
private void calcNotesCount() {
|
||||
mNotesCount = 0;
|
||||
for (int i = 0; i < getCount(); i++) {
|
||||
Cursor c = (Cursor) getItem(i);
|
||||
if (c != null) {
|
||||
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
|
||||
mNotesCount++;
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Invalid cursor");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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; // 用于显示提醒图标的 ImageView
|
||||
private TextView mTitle; // 显示标题的 TextView
|
||||
private TextView mTime; // 显示时间的 TextView
|
||||
private TextView mCallName; // 显示通话者名称的 TextView
|
||||
private NoteItemData mItemData; // 列表项关联的 NoteItemData 数据
|
||||
private CheckBox mCheckBox; // 用于选择模式的 CheckBox
|
||||
|
||||
public NotesListItem(Context context) {
|
||||
super(context); // 调用基类的构造方法
|
||||
inflate(context, R.layout.note_item, this); // 根据布局文件初始化视图
|
||||
// 初始化组件
|
||||
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
|
||||
mTitle = (TextView) findViewById(R.id.tv_title);
|
||||
mTime = (TextView) findViewById(R.id.tv_time);
|
||||
mCallName = (TextView) findViewById(R.id.tv_name);
|
||||
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
|
||||
}
|
||||
|
||||
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
|
||||
// 绑定数据到列表项视图
|
||||
// 如果处于选择模式并且数据类型是笔记,则显示 CheckBox
|
||||
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
|
||||
mCheckBox.setVisibility(View.VISIBLE);
|
||||
mCheckBox.setChecked(checked);
|
||||
} else {
|
||||
mCheckBox.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mItemData = data;
|
||||
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
|
||||
mCallName.setVisibility(View.GONE);
|
||||
mAlert.setVisibility(View.VISIBLE);
|
||||
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
|
||||
mTitle.setText(context.getString(R.string.call_record_folder_name)
|
||||
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
|
||||
mAlert.setImageResource(R.drawable.call_record);
|
||||
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
|
||||
mCallName.setVisibility(View.VISIBLE);
|
||||
mCallName.setText(data.getCallName());
|
||||
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
|
||||
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
|
||||
if (data.hasAlert()) {
|
||||
mAlert.setImageResource(R.drawable.clock);
|
||||
mAlert.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mAlert.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
mCallName.setVisibility(View.GONE);
|
||||
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
|
||||
|
||||
if (data.getType() == Notes.TYPE_FOLDER) {
|
||||
mTitle.setText(data.getSnippet()
|
||||
+ context.getString(R.string.format_folder_files_count,
|
||||
data.getNotesCount()));
|
||||
mAlert.setVisibility(View.GONE);
|
||||
} else {
|
||||
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
|
||||
if (data.hasAlert()) {
|
||||
mAlert.setImageResource(R.drawable.clock);
|
||||
mAlert.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mAlert.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
|
||||
|
||||
setBackground(data);
|
||||
}
|
||||
|
||||
private void setBackground(NoteItemData data) {
|
||||
int id = data.getBgColorId();
|
||||
if (data.getType() == Notes.TYPE_NOTE) {
|
||||
if (data.isSingle() || data.isOneFollowingFolder()) {
|
||||
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
|
||||
} else if (data.isLast()) {
|
||||
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
|
||||
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
|
||||
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
|
||||
} else {
|
||||
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
|
||||
}
|
||||
} else {
|
||||
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
|
||||
}
|
||||
}
|
||||
|
||||
public NoteItemData getItemData() {
|
||||
return mItemData; // 返回列表项关联的数据
|
||||
}
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
private void loadSyncButton() {
|
||||
// 获取同步按钮和最后同步时间文本视图
|
||||
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
|
||||
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
|
||||
|
||||
// 设置按钮状态
|
||||
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))); // 启用按钮
|
||||
|
||||
// 设置最后同步时间
|
||||
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() {
|
||||
// 刷新UI的方法
|
||||
loadAccountPreference(); // 加载账户偏好
|
||||
loadSyncButton(); // 加载同步按钮
|
||||
}
|
||||
|
||||
private void showSelectAccountAlertDialog() {
|
||||
// 显示选择账户的对话框
|
||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
|
||||
|
||||
// 省略了对话框标题和内容的设置代码...
|
||||
|
||||
// 获取可用的Google账户
|
||||
Account[] accounts = getGoogleAccounts();
|
||||
String defAccount = getSyncAccountName(this);
|
||||
|
||||
mOriAccounts = accounts; // 保存原始账户数组
|
||||
mHasAddedAccount = false; // 标记是否已添加账户
|
||||
|
||||
if (accounts.length > 0) {
|
||||
// 设置单选项目和点击事件
|
||||
dialogBuilder.setSingleChoiceItems(/* ... */);
|
||||
}
|
||||
|
||||
// 为对话框添加"添加账户"的视图和点击事件
|
||||
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
|
||||
dialogBuilder.setView(addAccountView);
|
||||
|
||||
final AlertDialog dialog = dialogBuilder.show();
|
||||
addAccountView.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// 添加账户的逻辑
|
||||
mHasAddedAccount = true;
|
||||
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
|
||||
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[]{"gmail-ls"});
|
||||
startActivityForResult(intent, -1);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void showChangeAccountConfirmAlertDialog() {
|
||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
|
||||
|
||||
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
|
||||
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
|
||||
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
|
||||
getSyncAccountName(this)));
|
||||
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
|
||||
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
|
||||
dialogBuilder.setCustomTitle(titleView);
|
||||
|
||||
CharSequence[] menuItemArray = new CharSequence[] {
|
||||
getString(R.string.preferences_menu_change_account),
|
||||
getString(R.string.preferences_menu_remove_account),
|
||||
getString(R.string.preferences_menu_cancel)
|
||||
};
|
||||
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == 0) {
|
||||
showSelectAccountAlertDialog();
|
||||
} else if (which == 1) {
|
||||
removeSyncAccount();
|
||||
refreshUI();
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogBuilder.show();
|
||||
}
|
||||
|
||||
private Account[] getGoogleAccounts() {
|
||||
AccountManager accountManager = AccountManager.get(this);
|
||||
return accountManager.getAccountsByType("com.google");
|
||||
}
|
||||
|
||||
private void setSyncAccount(String account) {
|
||||
if (!getSyncAccountName(this).equals(account)) {
|
||||
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
if (account != null) {
|
||||
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
|
||||
} else {
|
||||
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
|
||||
}
|
||||
editor.commit();
|
||||
|
||||
// clean up last sync time
|
||||
setLastSyncTime(this, 0);
|
||||
|
||||
// clean up local gtask related info
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(NoteColumns.GTASK_ID, "");
|
||||
values.put(NoteColumns.SYNC_ID, 0);
|
||||
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
|
||||
}
|
||||
}).start();
|
||||
|
||||
Toast.makeText(NotesPreferenceActivity.this,
|
||||
getString(R.string.preferences_toast_success_set_accout, account),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSyncAccount() {
|
||||
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
|
||||
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
|
||||
}
|
||||
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
|
||||
editor.remove(PREFERENCE_LAST_SYNC_TIME);
|
||||
}
|
||||
editor.commit();
|
||||
|
||||
// clean up local gtask related info
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(NoteColumns.GTASK_ID, "");
|
||||
values.put(NoteColumns.SYNC_ID, 0);
|
||||
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static String getSyncAccountName(Context context) {
|
||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
|
||||
Context.MODE_PRIVATE);
|
||||
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
|
||||
}
|
||||
|
||||
public static void setLastSyncTime(Context context, long time) {
|
||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
|
||||
Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public static long getLastSyncTime(Context context) {
|
||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
|
||||
Context.MODE_PRIVATE);
|
||||
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
|
||||
}
|
||||
|
||||
private class GTaskReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
refreshUI();
|
||||
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
|
||||
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
|
||||
syncStatus.setText(intent
|
||||
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
Intent intent = new Intent(this, NotesListActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.micode.notes.widget;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Context;
|
||||
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.tool.ResourceParser;
|
||||
|
||||
|
||||
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
// 当小部件需要更新时调用
|
||||
// 调用基类的update方法来执行更新逻辑
|
||||
super.update(context, appWidgetManager, appWidgetIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
// 返回2x小部件的布局资源ID
|
||||
// 该布局定义了小部件的界面结构
|
||||
return R.layout.widget_2x;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getBgResourceId(int bgId) {
|
||||
// 根据传入的背景ID获取2x小部件的背景资源ID
|
||||
// 使用ResourceParser.WidgetBgResources中的相应方法来解析背景资源
|
||||
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getWidgetType() {
|
||||
// 返回2x小部件的类型标识
|
||||
// 该标识用于区分不同类型的小部件
|
||||
return Notes.TYPE_WIDGET_2X;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue