From ffcb3b8979d4a1e235e47ef1fa37db870c046b71 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:23:15 +0800 Subject: [PATCH 01/40] ADD file via upload --- src/data/Contact.java | 91 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/data/Contact.java diff --git a/src/data/Contact.java b/src/data/Contact.java new file mode 100644 index 0000000..d249ae3 --- /dev/null +++ b/src/data/Contact.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.data; + +import android.content.Context; + +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; + +// 定义一个名为Contact的公开类,表示联系人。此类包含联系人相关的属性和方法。 +public class Contact { //联系人 + // 定义一个私有的静态的HashMap对象,用于缓存联系人信息。键为电话号码,值为联系人的名字。 + private static HashMap sContactCache; + + // 定义一个私有的常量String,作为日志的标签,方便调试。 + private static final String TAG = "Contact"; + + // 定义一个私有的静态常量字符串CALLER_ID_SELECTION,包含用于查询联系人的SQL语句。这个语句基于电话号码和MIME类型进行查询。 + // 这是一个复杂的查询,它从电话号码和MIME类型中选择联系人信息,并且它使用了子查询来获取匹配的电话号码。 + // 这个查询的具体含义和用法需要依赖它所在的上下文和相关的数据库表结构。 + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + ++ Phone.NUMBER // Phone.NUMBER应该是Phone类中的一个属性,表示电话号码。 + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" // 数据中的MIME类型等于电话内容项类型。这通常用于确定联系人的信息。 + + " AND " + Data.RAW_CONTACT_ID + " IN " // 数据中的原始联系人ID在...中 + + "(SELECT raw_contact_id " // 子查询选择所有的原始联系人ID + + " FROM phone_lookup" // 从phone_lookup表中 + + " WHERE min_match = '+')"; // 其中min_match等于'+'。这个min_match字段的含义需要依赖具体的数据库表结构。 + + // 定义一个公开的静态方法getContact,输入参数为Context(上下文对象)和String(电话号码),返回值为String(联系人的名字)。该方法用于获取联系人的名字。 + // 如果sContactCache中已经存在该电话号码的信息,则直接返回该信息。否则,查询数据库获取该电话号码对应的信息,并存入sContactCache中。 + // 如果查询出现异常或者没有找到相关信息,会返回null。 + // 获取联系人 + public static String getContact(Context context, String phoneNumber) { + // 如果sContactCache为空,则初始化它。 + if(sContactCache == null) { + sContactCache = new HashMap(); + } + + // 检查sContactCache中是否已经有该phoneNumber的信息。如果有,则直接返回该信息。 + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + + // 用电话号码替换CALLER_ID_SELECTION中的"+",并存入selection变量中。这个字符串是用于从数据库中查询信息的SQL语句。 + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 使用selection从数据库中查询phoneNumber的信息。查询的结果存储在Cursor对象中。 + // 查找数据库中phoneNumber的信息 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, // 数据内容URI,通常用于访问联系人数据。 + new String [] { Phone.DISPLAY_NAME }, // 要获取的字段,这里是要获取联系人的名字。 + selection, // 查询的条件,这里是电话号码和MIME类型。 + new String[] { phoneNumber }, // 查询的参数,这里是电话号码。 + null); // 排序方式,这里不排序。 + + // 判断查询结果。如果Cursor不为空且移动到第一条记录,则说明找到了相关信息。 + // moveToFirst()返回第一条 + if (cursor != null && cursor.moveToFirst()) { + try { + // 获取联系人的名字并存入sContactCache中,然后返回该名字。 + // 如果获取过程中出现异常,则捕获异常并记录错误日志,然后返回null。 + // 找到相关信息 + String name = cursor.getString(0); + sContactCache.put(phoneNumber, name); + return name; + // 异常 + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, " Cursor get string error " + e.toString()); // 记录错误日志。 + return null; // 返回null。 + } finally { + cursor.close(); // 关闭Cursor对象。释放资源。无论是否 From b793768fd37e4cd50860e43d7544c723b0fc9cc8 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:23:27 +0800 Subject: [PATCH 02/40] ADD file via upload --- src/data/Notes.java | 319 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 src/data/Notes.java diff --git a/src/data/Notes.java b/src/data/Notes.java new file mode 100644 index 0000000..e1bb4d6 --- /dev/null +++ b/src/data/Notes.java @@ -0,0 +1,319 @@ +/* + * 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.ContentUris; +import android.net.Uri; +// Notes 类中定义了很多常量,这些常量大多是int型和string型 +public class Notes { + public static final String AUTHORITY = "micode_notes"; + public static final String TAG = "Notes"; + + //以下三个常量对NoteColumns.TYPE的值进行设置时会用到 + public static final int TYPE_NOTE = 0; + public static final int TYPE_FOLDER = 1; + public static final int TYPE_SYSTEM = 2; + + /** + * Following IDs are system folders' identifiers + * {@link Notes#ID_ROOT_FOLDER } is default folder + * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder + * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + */ + public static final int ID_ROOT_FOLDER = 0; + public static final int ID_TEMPARAY_FOLDER = -1; + public static final int ID_CALL_RECORD_FOLDER = -2; + public static final int ID_TRASH_FOLER = -3; + + public static final String INTENT_EXTRA_ALERT_DATE = + +"net.micode.notes.alert_date"; + public static final String INTENT_EXTRA_BACKGROUND_ID = + +"net.micode.notes.background_color_id"; + public static final String INTENT_EXTRA_WIDGET_ID = + +"net.micode.notes.widget_id"; + public static final String INTENT_EXTRA_WIDGET_TYPE = + +"net.micode.notes.widget_type"; + public static final String INTENT_EXTRA_FOLDER_ID = + +"net.micode.notes.folder_id"; + public static final String INTENT_EXTRA_CALL_DATE = + +"net.micode.notes.call_date"; + + public static final int TYPE_WIDGET_INVALIDE = -1; + public static final int TYPE_WIDGET_2X = 0; + public static final int TYPE_WIDGET_4X = 1; + + public static class DataConstants { + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; + } + + /** + * Uri to query all notes and folders + */ + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + + +AUTHORITY + "/note");//定义查询便签和文件夹的指针。 + +// public static final Uri my_URI = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI , 10); + + /** + * Uri to query data + */ + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + + +AUTHORITY + "/data");//定义查找数据的指针。 + + // 定义NoteColumns的常量,用于后面创建数据库的表头 + public interface NoteColumns { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + public static final String ID = "_id"; + + /** + * The parent's id for note or folder + *

Type: INTEGER (long)

+ */ + public static final String PARENT_ID = "parent_id";//为什么会有parent_id + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + + + /** + * Alert date + *

Type: INTEGER (long)

+ */ + public static final String ALERTED_DATE = "alert_date"; + + /** + * Folder's name or text content of note + *

Type: TEXT

+ */ + public static final String SNIPPET = "snippet"; + + /** + * Note's widget id + *

Type: INTEGER (long)

+ */ + public static final String WIDGET_ID = "widget_id"; + + /** + * Note's widget type + *

Type: INTEGER (long)

+ */ + public static final String WIDGET_TYPE = "widget_type"; + + /** + * Note's background color's id + *

Type: INTEGER (long)

+ */ + public static final String BG_COLOR_ID = "bg_color_id"; + + /** + * For text note, it doesn't has attachment, for multi-media + * note, it has at least one attachment + *

Type: INTEGER

+ */ + public static final String HAS_ATTACHMENT = "has_attachment"; + + /** + * Folder's count of notes + *

Type: INTEGER (long)

+ */ + public static final String NOTES_COUNT = "notes_count"; + + /** + * The file type: folder or note + *

Type: INTEGER

+ */ + public static final String TYPE = "type"; + + /** + * The last sync id + *

Type: INTEGER (long)

+ */ + public static final String SYNC_ID = "sync_id";//同步 + + /** + * Sign to indicate local modified or not + *

Type: INTEGER

+ */ + public static final String LOCAL_MODIFIED = "local_modified"; + + /** + * Original parent id before moving into temporary folder + *

Type : INTEGER

+ */ + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + + /** + * The gtask id + *

Type : TEXT

+ */ + public static final String GTASK_ID = "gtask_id"; + + /** + * The version code + *

Type : INTEGER (long)

+ */ + public static final String VERSION = "version"; + }//这些常量主要是定义便签的属性的。 + + // 定义DataColumns的常量,用于后面创建数据库的表头 + public interface DataColumns { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + public static final String ID = "_id"; + + /** + * The MIME type of the item represented by this row. + *

Type: Text

+ */ + public static final String MIME_TYPE = "mime_type"; + + /** + * The reference id to note that this data belongs to + *

Type: INTEGER (long)

+ */ + public static final String NOTE_ID = "note_id"; + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + + /** + * Data's content + *

Type: TEXT

+ */ + public static final String CONTENT = "content"; + + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, +used for + * integer data type + *

Type: INTEGER

+ */ + public static final String DATA1 = "data1"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, +used for + * integer data type + *

Type: INTEGER

+ */ + public static final String DATA2 = "data2"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, +used for + * TEXT data type + *

Type: TEXT

+ */ + public static final String DATA3 = "data3"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, +used for + * TEXT data type + *

Type: TEXT

+ */ + public static final String DATA4 = "data4"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, +used for + * TEXT data type + *

Type: TEXT

+ */ + public static final String DATA5 = "data5"; + }//主要是定义存储便签内容数据的 + public static final class TextNote implements DataColumns { + /** + * Mode to indicate the text in check list mode or not + *

Type: Integer 1:check list mode 0: normal mode

+ */ + public static final String MODE = DATA1; + + public static final int MODE_CHECK_LIST = 1; + + public static final String CONTENT_TYPE = + +"vnd.android.cursor.dir/text_note"; + + public static final String CONTENT_ITEM_TYPE = + +"vnd.android.cursor.item/text_note"; + + public static final Uri CONTENT_URI = Uri.parse("content://" + + +AUTHORITY + "/text_note"); + }//文本内容的数据结构 + + public static final class CallNote implements DataColumns { + /** + * Call date for this record + *

Type: INTEGER (long)

+ */ + public static final String CALL_DATE = DATA1; + + /** + * Phone number for this record + *

Type: TEXT

+ */ + public static final String PHONE_NUMBER = DATA3; + + public static final String CONTENT_TYPE = + +"vnd.android.cursor.dir/call_note"; + + public static final String CONTENT_ITEM_TYPE = + +"vnd.android.cursor.item/call_note"; + + public static final Uri CONTENT_URI = Uri.parse("content://" + + +AUTHORITY + "/call_note"); + }//电话内容的数据结构 +} From 180d43dfb7f4c4bf4b338d4b8c9e731a938b1132 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:23:48 +0800 Subject: [PATCH 03/40] ADD file via upload --- src/data/NotesDatabaseHelper.java | 365 ++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 src/data/NotesDatabaseHelper.java diff --git a/src/data/NotesDatabaseHelper.java b/src/data/NotesDatabaseHelper.java new file mode 100644 index 0000000..dde87c5 --- /dev/null +++ b/src/data/NotesDatabaseHelper.java @@ -0,0 +1,365 @@ +/* + * 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;//就是用于保存一些数据(string boolean byte double float int long short ...)信息,这些信息可以被数据库操作时使用。 +import android.content.Context;//加载和访问资源。(android中主要是这两个功能,但是这里具体不清楚) +import android.database.sqlite.SQLiteDatabase;//主要提供了对应于添加、删除、更新、查询的操作方法: insert()、delete()、update()和query()。配合content.values +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; +//数据库操作,用SQLOpenhelper,对一些note和文件进行数据库的操作,比如删除文件后,将文件里的note也相应删除 + +public class NotesDatabaseHelper extends SQLiteOpenHelper { + private static final String DB_NAME = "note.db"; + + private static final int DB_VERSION = 4; + + public interface TABLE { //接口,分成note和data,在后面的程序里分别使用过 + public static final String NOTE = "note"; + + public static final String DATA = "data"; + } + + private static final String TAG = "NotesDatabaseHelper"; + + private static NotesDatabaseHelper mInstance; + + 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" + + ")";//数据库中需要存储的项目的名称,就相当于创建一个表格的表头的内容。 + + 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 ''" + + ")";//和上面的功能一样,主要是存储的项目不同 + + private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";//存储便签编号的一个数据表格 + + /** + * Increase folder's note count when move note to the folder + */ + 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";//在文件夹中移入一个Note之后需要更改的数据的表格。 + + /** + * Decrease folder's note count when move note from folder + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " END";//在文件夹中移出一个Note之后需要更改的数据的表格。 + + /** + * Increase folder's note count when insert new note to the folder + */ + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_insert " + + " AFTER INSERT ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END";//在文件夹中插入一个Note之后需要更改的数据的表格。 + + /** + * Decrease folder's note count when delete note from the folder + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " END";//在文件夹中删除一个Note之后需要更改的数据的表格。 + + /** + * Update note's content when insert data with type {@link DataConstants#NOTE} + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = + "CREATE TRIGGER update_note_content_on_insert " + + " AFTER INSERT ON " + TABLE.DATA + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END";//在文件夹中对一个Note导入新的数据之后需要更改的数据的表格。 + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has changed + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER update_note_content_on_update " + + " AFTER UPDATE ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END";//Note数据被修改后需要更改的数据的表格。 + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has deleted + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = + "CREATE TRIGGER update_note_content_on_delete " + + " AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END";//Note数据被删除后需要更改的数据的表格。 + + /** + * Delete datas belong to note which has been deleted + */ + private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END";//删除已删除的便签的数据后需要更改的数据的表格。 + + /** + * Delete notes belong to folder which has been deleted + */ + private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = + "CREATE TRIGGER folder_delete_notes_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END";//删除已删除的文件夹的便签后需要更改的数据的表格。 + + /** + * Move notes belong to folder which has been moved to trash folder + */ + private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = + "CREATE TRIGGER folder_move_notes_on_trash " + + " AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_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); + }//execSQL是数据库操作的API,主要是更改行为的SQL语句。 + //在这里主要是用来重新创建上述定义的表格用的,先删除原来有的数据库的触发器再重新创建新的数据库 + + private void createSystemFolder(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + + /** + * call record foler for call notes + */ + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * root folder which is default folder + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * temporary folder which is used for moving note + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * create trash folder + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + }//创建几个系统文件夹 + + 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); + }//同上面的execSQL + + 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; // this upgrade including the upgrade from v2 to v3 + 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); + }//更新到V2版本 + + private void upgradeToV3(SQLiteDatabase db) { + // drop unused triggers + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); + // add a column for gtask id + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + + " TEXT NOT NULL DEFAULT ''"); + // add a trash system folder + ContentValues values = new ContentValues(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + }//更新到V3版本 + + private void upgradeToV4(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + + " INTEGER NOT NULL DEFAULT 0"); + }//更新到V4版本 +} From d1dafad8b678fb74950de8f168060b746e6e3ccd Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:23:56 +0800 Subject: [PATCH 04/40] ADD file via upload --- src/data/NotesProvider.java | 336 ++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 src/data/NotesProvider.java diff --git a/src/data/NotesProvider.java b/src/data/NotesProvider.java new file mode 100644 index 0000000..437af5a --- /dev/null +++ b/src/data/NotesProvider.java @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.data; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; +//为存储和获取数据提供接口。可以在不同的应用程序之间共享数据 +//ContentProvider提供的方法 +//query:查询 +//insert:插入 +//update:更新 +//delete:删除 +//getType:得到数据类型 +public class NotesProvider extends ContentProvider { + // UriMatcher用于匹配Uri + private static final UriMatcher mMatcher; + + private NotesDatabaseHelper mHelper; + + private static final String TAG = "NotesProvider"; + + private static final int URI_NOTE = 1; + private static final int URI_NOTE_ITEM = 2; + private static final int URI_DATA = 3; + private static final int URI_DATA_ITEM = 4; + + private static final int URI_SEARCH = 5; + private static final int URI_SEARCH_SUGGEST = 6; + + static { + // 创建UriMatcher时,调用UriMatcher(UriMatcher.NO_MATCH)表示不匹配任何路径的返回码 + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + // 把需要匹配Uri路径全部给注册上 + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + } + + /** + * x'0A' represents the '\n' character in sqlite. For title and content in the search result, + * we will trim '\n' and white space in order to show more information. + */ + // 声明 NOTES_SEARCH_PROJECTION + private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + // 声明NOTES_SNIPPET_SEARCH_QUERY + private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + @Override + // Context只有在onCreate()中才被初始化 + // 对mHelper进行实例化 + public boolean onCreate() { + mHelper = NotesDatabaseHelper.getInstance(getContext()); + return true; + } + + @Override + // 查询uri在数据库中对应的位置 + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + Cursor c = null; + // 获取可读数据库 + SQLiteDatabase db = mHelper.getReadableDatabase(); + String id = null; + // 匹配查找uri + switch (mMatcher.match(uri)) { + // 对于不同的匹配值,在数据库中查找相应的条目 + case URI_NOTE: + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + case URI_DATA: + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + case URI_SEARCH: + case URI_SEARCH_SUGGEST: + if (sortOrder != null || projection != null) { + // 不合法的参数异常 + throw new IllegalArgumentException( + "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); + } + + String searchString = null; + if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + if (uri.getPathSegments().size() > 1) { + // getPathSegments()方法得到一个String的List, + // 在uri.getPathSegments().get(1)为第2个元素 + searchString = uri.getPathSegments().get(1); + } + } else { + searchString = uri.getQueryParameter("pattern"); + } + + if (TextUtils.isEmpty(searchString)) { + return null; + } + + try { + searchString = String.format("%%%s%%", searchString); + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, + new String[] { searchString }); + } catch (IllegalStateException ex) { + Log.e(TAG, "got exception: " + ex.toString()); + } + break; + default: + // 抛出异常 + throw new IllegalArgumentException("Unknown URI " + uri); + } + if (c != null) { + c.setNotificationUri(getContext().getContentResolver(), uri); + } + return c; + } + + @Override + // 插入一个uri + public Uri insert(Uri uri, ContentValues values) { + // 获得可写的数据库 + SQLiteDatabase db = mHelper.getWritableDatabase(); + long dataId = 0, noteId = 0, insertedId = 0; + switch (mMatcher.match(uri)) { + // 新增一个条目 + case URI_NOTE: + insertedId = noteId = db.insert(TABLE.NOTE, null, values); + break; + // 如果存在,查找NOTE_ID + case URI_DATA: + if (values.containsKey(DataColumns.NOTE_ID)) { + noteId = values.getAsLong(DataColumns.NOTE_ID); + } else { + Log.d(TAG, "Wrong data format without note id:" + values.toString()); + } + insertedId = dataId = db.insert(TABLE.DATA, null, values); + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + // Notify the note uri + // notifyChange获得一个ContextResolver对象并且更新里面的内容 + if (noteId > 0) { + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); + } + + // Notify the data uri + if (dataId > 0) { + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); + } + + // 返回插入的uri的路径 + return ContentUris.withAppendedId(uri, insertedId); + } + + @Override + // 删除一个uri + public int delete(Uri uri, String selection, String[] selectionArgs) { + //Uri代表要操作的数据,Android上可用的每种资源 -包括 图像、视频片段、音频资源等都可以用Uri来表示。 + int count = 0; + String id = null; + // 获得可写的数据库 + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean deleteData = false; + switch (mMatcher.match(uri)) { + case URI_NOTE: + selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; + count = db.delete(TABLE.NOTE, selection, selectionArgs); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + /** + * ID that smaller than 0 is system folder which is not allowed to + * trash + */ + long noteId = Long.valueOf(id); + if (noteId <= 0) { + break; + } + count = db.delete(TABLE.NOTE, + NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + break; + case URI_DATA: + count = db.delete(TABLE.DATA, selection, selectionArgs); + deleteData = true; + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + count = db.delete(TABLE.DATA, + DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + deleteData = true; + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + if (count > 0) { + if (deleteData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } + getContext().getContentResolver().notifyChange(uri, null); + } + return count; + } + + @Override + // 更新一个uri + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int count = 0; + String id = null; + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean updateData = false; + switch (mMatcher.match(uri)) { + case URI_NOTE: + increaseNoteVersion(-1, selection, selectionArgs); + count = db.update(TABLE.NOTE, values, selection, selectionArgs); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + break; + case URI_DATA: + count = db.update(TABLE.DATA, values, selection, selectionArgs); + updateData = true; + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + updateData = true; + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + + if (count > 0) { + if (updateData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } + getContext().getContentResolver().notifyChange(uri, null); + } + return count; + } + + // 将字符串解析成规定格式 + private String parseSelection(String selection) { + return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + } + + //增加一个noteVersion + private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(TABLE.NOTE); + sql.append(" SET "); + sql.append(NoteColumns.VERSION); + sql.append("=" + NoteColumns.VERSION + "+1 "); + + if (id > 0 || !TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + } + if (id > 0) { + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); + } + if (!TextUtils.isEmpty(selection)) { + String selectString = id > 0 ? parseSelection(selection) : selection; + for (String args : selectionArgs) { + selectString = selectString.replaceFirst("\\?", args); + } + sql.append(selectString); + } + + // execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句 + mHelper.getWritableDatabase().execSQL(sql.toString()); + } + + @Override + public String getType(Uri uri) { + // TODO Auto-generated method stub + return null; + } + +} \ No newline at end of file From 9e3dc4953d89d470563bf0ccdf123acd830b53dc Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:24:33 +0800 Subject: [PATCH 05/40] ADD file via upload --- src/gtask/data/MetaData.java | 130 +++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/gtask/data/MetaData.java diff --git a/src/gtask/data/MetaData.java b/src/gtask/data/MetaData.java new file mode 100644 index 0000000..211fd11 --- /dev/null +++ b/src/gtask/data/MetaData.java @@ -0,0 +1,130 @@ +/* + * 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. + */ + + +import android.database.Cursor; +import android.util.Log; + +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONException; +import org.json.JSONObject; + + +package net.micode.notes.gtask.data; + +public class MetaData extends Task { + /* + * 功能描述:得到类的简写名称存入字符串TAG中 + * 实现过程:调用getSimpleName ()函数 + */ + private final static String TAG = MetaData.class.getSimpleName(); + private String mRelatedGid = null; + /* + * 功能描述:设置数据,即生成元数据库 + * 实现过程:调用JSONObject库函数put (),Task类中的setNotes ()和setName ()函数 + * 参数注解: + */ + public void setMeta(String gid, JSONObject metaInfo) + { + //对函数块进行注释 + try { + metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); + /* + * 将这对键值放入metaInfo这个jsonobject对象中 + */ + } catch (JSONException e) { + Log.e(TAG, "failed to put related gid"); + /* + * 输出错误信息 + */ + } + setNotes(metaInfo.toString()); + setName(GTaskStringUtils.META_NOTE_NAME); + } + /* + * 功能描述:获取相关联的Gid + */ + public String getRelatedGid() { + return mRelatedGid; + } + /* + * 功能描述:判断当前数据是否为空,若为空则返回真即值得保存 + * Made By CuiCan + */ + @Override + public boolean isWorthSaving() { + return getNotes() != null; + } + /* + * 功能描述:使用远程json数据对象设置元数据内容 + * 实现过程:调用父类Task中的setContentByRemoteJSON ()函数,并 + * 参数注解: + */ + @Override + public void setContentByRemoteJSON(JSONObject js) { + super.setContentByRemoteJSON(js); + if (getNotes() != null) { + try { + JSONObject metaInfo = new JSONObject(getNotes().trim()); + mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); + } catch (JSONException e) { + Log.w(TAG, "failed to get related gid"); + /* + * 输出警告信息 + */ + mRelatedGid = null; + } + } + } + /* + * 功能描述:使用本地json数据对象设置元数据内容,一般不会用到,若用到,则抛出异常 + * Made By CuiCan + */ + @Override + public void setContentByLocalJSON(JSONObject js) { + // this function should not be called + throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); + /* + * 传递非法参数异常 + */ + } + /* + * 功能描述:从元数据内容中获取本地json对象,一般不会用到,若用到,则抛出异常 + * Made By CuiCan + */ + @Override + public JSONObject getLocalJSONFromContent() { + throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); + /* + * 传递非法参数异常 + * Made By Cui Can + */ + } + /* + * 功能描述:获取同步动作状态,一般不会用到,若用到,则抛出异常 + * Made By CuiCan + */ + @Override + public int getSyncAction(Cursor c) { + throw new IllegalAccessError("MetaData:getSyncAction should not be called"); + /* + * 传递非法参数异常 + * Made By Cui Can + */ + } + +} \ No newline at end of file From 6abe5c9ed9911a86cb98f4df459847f77ec404e7 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:24:53 +0800 Subject: [PATCH 06/40] ADD file via upload --- src/gtask/data/Node.java | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/gtask/data/Node.java diff --git a/src/gtask/data/Node.java b/src/gtask/data/Node.java new file mode 100644 index 0000000..48dca8e --- /dev/null +++ b/src/gtask/data/Node.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package net.micode.notes.gtask.data; + +import android.database.Cursor; + +import org.json.JSONObject; + +/** + * 应该是同步操作的基础数据类型,定义了相关指示同步操作的常量 + * 关键字:abstract + */ +public abstract class Node { + //定义了各种用于表征同步状态的常量 + public static final int SYNC_ACTION_NONE = 0;// 本地和云端都无可更新内容(即本地和云端内容一致) + + public static final int SYNC_ACTION_ADD_REMOTE = 1;// 需要在远程云端增加内容 + + public static final int SYNC_ACTION_ADD_LOCAL = 2;// 需要在本地增加内容 + + public static final int SYNC_ACTION_DEL_REMOTE = 3;// 需要在远程云端删除内容 + + public static final int SYNC_ACTION_DEL_LOCAL = 4;// 需要在本地删除内容 + + public static final int SYNC_ACTION_UPDATE_REMOTE = 5;// 需要将本地内容更新到远程云端 + + public static final int SYNC_ACTION_UPDATE_LOCAL = 6;// 需要将远程云端内容更新到本地 + + public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;// 同步出现冲突 + + public static final int SYNC_ACTION_ERROR = 8;// 同步出现错误 + + private String mGid; + + private String mName; + + private long mLastModified;//记录最后一次修改时间 + + private boolean mDeleted;//表征是否被删除 + + public Node() { + mGid = null; + mName = ""; + mLastModified = 0; + mDeleted = false; + } + + public abstract JSONObject getCreateAction(int actionId); + + public abstract JSONObject getUpdateAction(int actionId); + + public abstract void setContentByRemoteJSON(JSONObject js); + + public abstract void setContentByLocalJSON(JSONObject js); + + public abstract JSONObject getLocalJSONFromContent(); + + public abstract int getSyncAction(Cursor c); + + public void setGid(String gid) { + this.mGid = gid; + } + + public void setName(String name) { + this.mName = name; + } + + public void setLastModified(long lastModified) { + this.mLastModified = lastModified; + } + + public void setDeleted(boolean deleted) { + this.mDeleted = deleted; + } + + public String getGid() { + return this.mGid; + } + + public String getName() { + return this.mName; + } + + public long getLastModified() { + return this.mLastModified; + } + + public boolean getDeleted() { + return this.mDeleted; + } + +} \ No newline at end of file From 711494b425c971ba5496bfa697dc2bf6bf81879a Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:25:05 +0800 Subject: [PATCH 07/40] ADD file via upload --- src/gtask/data/SqlData.java | 261 ++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 src/gtask/data/SqlData.java diff --git a/src/gtask/data/SqlData.java b/src/gtask/data/SqlData.java new file mode 100644 index 0000000..9d8db94 --- /dev/null +++ b/src/gtask/data/SqlData.java @@ -0,0 +1,261 @@ +/* + * 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. + */ + + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; +import net.micode.notes.gtask.exception.ActionFailureException; + +import org.json.JSONException; +import org.json.JSONObject; + + + +/* + * Description:用于支持小米便签最底层的数据库相关操作,和sqlnote的关系上是子集关系,即data是note的子集(节点)。 + * SqlData其实就是也就是所谓数据中的数据 + */ + +package net.micode.notes.gtask.data; +/* + * 功能描述: + * 实现过程: + * 参数注解: + * Made By CuiCan + */ + +public class SqlData { + /* + * 功能描述:得到类的简写名称存入字符串TAG中 + * 实现过程:调用getSimpleName ()函数 + * Made By CuiCan + */ + private static final String TAG = SqlData.class.getSimpleName(); + + private static final int INVALID_ID = -99999;//为mDataId置初始值-99999 + + + /** + * 来自Notes类中定义的DataColumn中的一些常量 + */ + + // 集合了interface DataColumns中所有SF常量 + public static final String[] PROJECTION_DATA = new String[] { + DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, + DataColumns.DATA3 + }; + + /** + * 以下五个变量作为sql表中5列的编号 + */ + public static final int DATA_ID_COLUMN = 0; + + public static final int DATA_MIME_TYPE_COLUMN = 1; + + public static final int DATA_CONTENT_COLUMN = 2; + + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; + + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + + private ContentResolver mContentResolver; + //判断是否直接用Content生成,是为true,否则为false + private boolean mIsCreate; + + private long mDataId; + + private String mDataMimeType; + + private String mDataContent; + + private long mDataContentData1; + + private String mDataContentData3; + + private ContentValues mDiffDataValues; + + /* + * 功能描述:构造函数,用于初始化数据 + * 参数注解:mContentResolver用于获取ContentProvider提供的数据 + * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) + * 参数注解: + * Made By CuiCan + */ + public SqlData(Context context) { + mContentResolver = context.getContentResolver(); + mIsCreate = true; + mDataId = INVALID_ID;//mDataId置初始值-99999 + mDataMimeType = DataConstants.NOTE; + mDataContent = ""; + mDataContentData1 = 0; + mDataContentData3 = ""; + mDiffDataValues = new ContentValues(); + } + + + /* + * 功能描述:构造函数,初始化数据 + * 参数注解:mContentResolver用于获取ContentProvider提供的数据 + * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) + * 参数注解: + * Made By CuiCan + */ + public SqlData(Context context, Cursor c) { + mContentResolver = context.getContentResolver(); + mIsCreate = false; + loadFromCursor(c); + mDiffDataValues = new ContentValues(); + } + + /* + * 功能描述:从光标处加载数据 + * 从当前的光标处将五列的数据加载到该类的对象 + * Made By CuiCan + */ + private void loadFromCursor(Cursor c) { + mDataId = c.getLong(DATA_ID_COLUMN); + mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); + mDataContent = c.getString(DATA_CONTENT_COLUMN); + mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); + mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); + } + + + /* + * 功能描述:设置用于共享的数据,并提供异常抛出与处理机制 + * 参数注解: + * Made By CuiCan + */ + public void setContent(JSONObject js) throws JSONException { + //如果传入的JSONObject对象中有DataColumns.ID这一项,则设置,否则设为INVALID_ID + long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; + if (mIsCreate || mDataId != dataId) { + mDiffDataValues.put(DataColumns.ID, dataId); + } + mDataId = dataId; + + String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) + : DataConstants.NOTE; + if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { + mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); + } + mDataMimeType = dataMimeType; + + String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; + if (mIsCreate || !mDataContent.equals(dataContent)) { + mDiffDataValues.put(DataColumns.CONTENT, dataContent); + } + mDataContent = dataContent; + + long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; + if (mIsCreate || mDataContentData1 != dataContentData1) { + mDiffDataValues.put(DataColumns.DATA1, dataContentData1); + } + mDataContentData1 = dataContentData1; + + String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; + if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { + mDiffDataValues.put(DataColumns.DATA3, dataContentData3); + } + mDataContentData3 = dataContentData3; + } + + + /* + * 功能描述:获取共享的数据内容,并提供异常抛出与处理机制 + * 参数注解: + * Made By CuiCan + */ + public JSONObject getContent() throws JSONException { + if (mIsCreate) { + Log.e(TAG, "it seems that we haven't created this in database yet"); + return null; + } + //创建JSONObject对象。并将相关数据放入其中,并返回。 + JSONObject js = new JSONObject(); + js.put(DataColumns.ID, mDataId); + js.put(DataColumns.MIME_TYPE, mDataMimeType); + js.put(DataColumns.CONTENT, mDataContent); + js.put(DataColumns.DATA1, mDataContentData1); + js.put(DataColumns.DATA3, mDataContentData3); + return js; + } + + /* + * 功能描述:commit函数用于把当前造作所做的修改保存到数据库 + * 参数注解: + * Made By CuiCan + */ + public void commit(long noteId, boolean validateVersion, long version) { + + if (mIsCreate) { + if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { + mDiffDataValues.remove(DataColumns.ID); + } + + mDiffDataValues.put(DataColumns.NOTE_ID, noteId); + Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); + try { + mDataId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + Log.e(TAG, "Get note id error :" + e.toString()); + throw new ActionFailureException("create note failed"); + } + } else { + if (mDiffDataValues.size() > 0) { + int result = 0; + if (!validateVersion) { + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); + } else { + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, + " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { + String.valueOf(noteId), String.valueOf(version) + }); + } + if (result == 0) { + Log.w(TAG, "there is no update. maybe user updates note when syncing"); + } + } + } + + mDiffDataValues.clear(); + mIsCreate = false; + } + + /* + * 功能描述:获取当前id + * 实现过程: + * 参数注解: + * Made By CuiCan + */ + public long getId() { + return mDataId; + } +} \ No newline at end of file From 9a34f918ec740d05f297b9799d78dfa7f4597a3a Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:25:18 +0800 Subject: [PATCH 08/40] ADD file via upload --- src/gtask/data/SqlNote.java | 265 ++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 src/gtask/data/SqlNote.java diff --git a/src/gtask/data/SqlNote.java b/src/gtask/data/SqlNote.java new file mode 100644 index 0000000..cab59ff --- /dev/null +++ b/src/gtask/data/SqlNote.java @@ -0,0 +1,265 @@ +/* + * 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. + */ + + +import android.appwidget.AppWidgetManager; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; +import net.micode.notes.tool.ResourceParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + + + + +/* + * Description:用于支持小米便签最底层的数据库相关操作,和sqlnote的关系上是子集关系,即data是note的子集(节点)。 + * SqlData其实就是也就是所谓数据中的数据 + */ + +package net.micode.notes.gtask.data; +/* + * 功能描述: + * 实现过程: + * 参数注解: + * Made By CuiCan + */ + +public class SqlData { + /* + * 功能描述:得到类的简写名称存入字符串TAG中 + * 实现过程:调用getSimpleName ()函数 + * Made By CuiCan + */ + private static final String TAG = SqlData.class.getSimpleName(); + + private static final int INVALID_ID = -99999;//为mDataId置初始值-99999 + + + /** + * 来自Notes类中定义的DataColumn中的一些常量 + */ + + // 集合了interface DataColumns中所有SF常量 + public static final String[] PROJECTION_DATA = new String[] { + DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, + DataColumns.DATA3 + }; + + /** + * 以下五个变量作为sql表中5列的编号 + */ + public static final int DATA_ID_COLUMN = 0; + + public static final int DATA_MIME_TYPE_COLUMN = 1; + + public static final int DATA_CONTENT_COLUMN = 2; + + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; + + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + + private ContentResolver mContentResolver; + //判断是否直接用Content生成,是为true,否则为false + private boolean mIsCreate; + + private long mDataId; + + private String mDataMimeType; + + private String mDataContent; + + private long mDataContentData1; + + private String mDataContentData3; + + private ContentValues mDiffDataValues; + + /* + * 功能描述:构造函数,用于初始化数据 + * 参数注解:mContentResolver用于获取ContentProvider提供的数据 + * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) + * 参数注解: + * Made By CuiCan + */ + public SqlData(Context context) { + mContentResolver = context.getContentResolver(); + mIsCreate = true; + mDataId = INVALID_ID;//mDataId置初始值-99999 + mDataMimeType = DataConstants.NOTE; + mDataContent = ""; + mDataContentData1 = 0; + mDataContentData3 = ""; + mDiffDataValues = new ContentValues(); + } + + + /* + * 功能描述:构造函数,初始化数据 + * 参数注解:mContentResolver用于获取ContentProvider提供的数据 + * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) + * 参数注解: + * Made By CuiCan + */ + public SqlData(Context context, Cursor c) { + mContentResolver = context.getContentResolver(); + mIsCreate = false; + loadFromCursor(c); + mDiffDataValues = new ContentValues(); + } + + /* + * 功能描述:从光标处加载数据 + * 从当前的光标处将五列的数据加载到该类的对象 + * Made By CuiCan + */ + private void loadFromCursor(Cursor c) { + mDataId = c.getLong(DATA_ID_COLUMN); + mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); + mDataContent = c.getString(DATA_CONTENT_COLUMN); + mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); + mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); + } + + + /* + * 功能描述:设置用于共享的数据,并提供异常抛出与处理机制 + * 参数注解: + * Made By CuiCan + */ + public void setContent(JSONObject js) throws JSONException { + //如果传入的JSONObject对象中有DataColumns.ID这一项,则设置,否则设为INVALID_ID + long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; + if (mIsCreate || mDataId != dataId) { + mDiffDataValues.put(DataColumns.ID, dataId); + } + mDataId = dataId; + + String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) + : DataConstants.NOTE; + if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { + mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); + } + mDataMimeType = dataMimeType; + + String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; + if (mIsCreate || !mDataContent.equals(dataContent)) { + mDiffDataValues.put(DataColumns.CONTENT, dataContent); + } + mDataContent = dataContent; + + long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; + if (mIsCreate || mDataContentData1 != dataContentData1) { + mDiffDataValues.put(DataColumns.DATA1, dataContentData1); + } + mDataContentData1 = dataContentData1; + + String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; + if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { + mDiffDataValues.put(DataColumns.DATA3, dataContentData3); + } + mDataContentData3 = dataContentData3; + } + + + /* + * 功能描述:获取共享的数据内容,并提供异常抛出与处理机制 + * 参数注解: + * Made By CuiCan + */ + public JSONObject getContent() throws JSONException { + if (mIsCreate) { + Log.e(TAG, "it seems that we haven't created this in database yet"); + return null; + } + //创建JSONObject对象。并将相关数据放入其中,并返回。 + JSONObject js = new JSONObject(); + js.put(DataColumns.ID, mDataId); + js.put(DataColumns.MIME_TYPE, mDataMimeType); + js.put(DataColumns.CONTENT, mDataContent); + js.put(DataColumns.DATA1, mDataContentData1); + js.put(DataColumns.DATA3, mDataContentData3); + return js; + } + + /* + * 功能描述:commit函数用于把当前造作所做的修改保存到数据库 + * 参数注解: + * Made By CuiCan + */ + public void commit(long noteId, boolean validateVersion, long version) { + + if (mIsCreate) { + if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { + mDiffDataValues.remove(DataColumns.ID); + } + + mDiffDataValues.put(DataColumns.NOTE_ID, noteId); + Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); + try { + mDataId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + Log.e(TAG, "Get note id error :" + e.toString()); + throw new ActionFailureException("create note failed"); + } + } else { + if (mDiffDataValues.size() > 0) { + int result = 0; + if (!validateVersion) { + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); + } else { + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, + " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { + String.valueOf(noteId), String.valueOf(version) + }); + } + if (result == 0) { + Log.w(TAG, "there is no update. maybe user updates note when syncing"); + } + } + } + + mDiffDataValues.clear(); + mIsCreate = false; + } + + /* + * 功能描述:获取当前id + * 实现过程: + * 参数注解: + * Made By CuiCan + */ + public long getId() { + return mDataId; + } +} \ No newline at end of file From f40a2c910f576109ca477c6859c3229c9314c75a Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:25:26 +0800 Subject: [PATCH 09/40] ADD file via upload --- src/gtask/data/Task.java | 358 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 src/gtask/data/Task.java diff --git a/src/gtask/data/Task.java b/src/gtask/data/Task.java new file mode 100644 index 0000000..ac50bc2 --- /dev/null +++ b/src/gtask/data/Task.java @@ -0,0 +1,358 @@ +/* + * 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. + */ + + +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; + + + + +package net.micode.notes.gtask.data; + +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;//对应的优先兄弟Task的指针(待完善) + + private TaskList mParent;//所在的任务列表的指针 + + public Task() { + super(); + mCompleted = false; + mNotes = null; + mPriorSibling = null;//TaskList中当前Task前面的Task的指针 + mParent = null;//当前Task所在的TaskList + mMetaInfo = null; + } + + public JSONObject getCreateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // index + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + 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); + + // parent_id + if (mParent!= null) { + js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); + } + + // dest_parent_type + js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + + // list_id + if (mParent!= null) { + js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); + } + + // prior_sibling_id + if (mPriorSibling != null) { + js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); + } + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-create jsonobject"); + } + + return js; + } + + public JSONObject getUpdateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // id + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + if (getNotes() != null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-update jsonobject"); + } + + return js; + } + + public void setContentByRemoteJSON(JSONObject js) { + if (js != null) { + try { + // id + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // last_modified + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // name + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + // notes + if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { + setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); + } + + // deleted + if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { + setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); + } + + // completed + if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { + setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to get task content from jsonobject"); + } + } + } + + public void setContentByLocalJSON(JSONObject js) { // metadata ʵʩ + if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) + || !js.has(GTaskStringUtils.META_HEAD_DATA)) { + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + + try { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { + Log.e(TAG, "invalid type"); + return; + } + + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + setName(data.getString(DataColumns.CONTENT)); + break; + } + } + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + } + + public JSONObject getLocalJSONFromContent() { + String name = getName(); + try { + if (mMetaInfo == null) { + // new task created from web + if (name == null) { + Log.w(TAG, "the note seems to be an empty one"); + return null; + } + + JSONObject js = new JSONObject(); + JSONObject note = new JSONObject(); + JSONArray dataArray = new JSONArray(); + JSONObject data = new JSONObject(); + data.put(DataColumns.CONTENT, name); + dataArray.put(data); + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + return js; + } else { + // synced task + JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + data.put(DataColumns.CONTENT, getName()); + break; + } + } + + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + return mMetaInfo; + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + 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; + } + } + } + + 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; + } + + // validate the note id now + if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { + Log.w(TAG, "note id doesn't match"); + return SYNC_ACTION_UPDATE_LOCAL; + } + + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // there is no local update + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // no update both side + return SYNC_ACTION_NONE; + } else { + // apply remote to local + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // validate gtask id + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; + } + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // local modification only + return SYNC_ACTION_UPDATE_REMOTE; + } else { + return SYNC_ACTION_UPDATE_CONFLICT; + } + } + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + + return SYNC_ACTION_ERROR; + } + + public boolean isWorthSaving() { + return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) + || (getNotes() != null && getNotes().trim().length() > 0); + } + + public void setCompleted(boolean completed) { + this.mCompleted = completed; + } + + 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; + } + +} \ No newline at end of file From 7776c5733ae5323e921f46a94882ed73c888a3d9 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:25:34 +0800 Subject: [PATCH 10/40] ADD file via upload --- src/gtask/data/TaskList.java | 356 +++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 src/gtask/data/TaskList.java diff --git a/src/gtask/data/TaskList.java b/src/gtask/data/TaskList.java new file mode 100644 index 0000000..af7b838 --- /dev/null +++ b/src/gtask/data/TaskList.java @@ -0,0 +1,356 @@ +/* + * 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. + */ + + +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; + + + + +package net.micode.notes.gtask.data; + +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;//对应的优先兄弟Task的指针(待完善) + + private TaskList mParent;//所在的任务列表的指针 + + public Task() { + super(); + mCompleted = false; + mNotes = null; + mPriorSibling = null;//TaskList中当前Task前面的Task的指针 + mParent = null;//当前Task所在的TaskList + mMetaInfo = null; + } + + public JSONObject getCreateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // index + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + 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); + + // parent_id + if (mParent!= null) { + js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); + } + + // dest_parent_type + js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + + // list_id + if (mParent!= null) { + js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); + } + + // prior_sibling_id + if (mPriorSibling != null) { + js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); + } + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-create jsonobject"); + } + + return js; + } + + public JSONObject getUpdateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // id + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + if (getNotes() != null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-update jsonobject"); + } + + return js; + } + + public void setContentByRemoteJSON(JSONObject js) { + if (js != null) { + try { + // id + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // last_modified + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // name + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + // notes + if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { + setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); + } + + // deleted + if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { + setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); + } + + // completed + if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { + setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to get task content from jsonobject"); + } + } + } + + public void setContentByLocalJSON(JSONObject js) { // metadata ʵʩ + if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) + || !js.has(GTaskStringUtils.META_HEAD_DATA)) { + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + + try { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { + Log.e(TAG, "invalid type"); + return; + } + + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + setName(data.getString(DataColumns.CONTENT)); + break; + } + } + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + } + + public JSONObject getLocalJSONFromContent() { + String name = getName(); + try { + if (mMetaInfo == null) { + // new task created from web + if (name == null) { + Log.w(TAG, "the note seems to be an empty one"); + return null; + } + + JSONObject js = new JSONObject(); + JSONObject note = new JSONObject(); + JSONArray dataArray = new JSONArray(); + JSONObject data = new JSONObject(); + data.put(DataColumns.CONTENT, name); + dataArray.put(data); + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + return js; + } else { + // synced task + JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + data.put(DataColumns.CONTENT, getName()); + break; + } + } + + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + return mMetaInfo; + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + 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; + } + } + } + + 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; + } + + // validate the note id now + if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { + Log.w(TAG, "note id doesn't match"); + return SYNC_ACTION_UPDATE_LOCAL; + } + + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // there is no local update + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // no update both side + return SYNC_ACTION_NONE; + } else { + // apply remote to local + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // validate gtask id + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; + } + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // local modification only + return SYNC_ACTION_UPDATE_REMOTE; + } else { + return SYNC_ACTION_UPDATE_CONFLICT; + } + } + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + + return SYNC_ACTION_ERROR; + } + + public boolean isWorthSaving() { + return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) + || (getNotes() != null && getNotes().trim().length() > 0); + } + + public void setCompleted(boolean completed) { + this.mCompleted = completed; + } + + 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; + } + +} \ No newline at end of file From b97d43042e6b8b451bb67bfda69a4d25003c1a41 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:26:01 +0800 Subject: [PATCH 11/40] ADD file via upload --- .../exception/ActionFailureException.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/gtask/exception/ActionFailureException.java diff --git a/src/gtask/exception/ActionFailureException.java b/src/gtask/exception/ActionFailureException.java new file mode 100644 index 0000000..15504be --- /dev/null +++ b/src/gtask/exception/ActionFailureException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.exception; + +public class ActionFailureException extends RuntimeException { + private static final long serialVersionUID = 4425249765923293627L; + + public ActionFailureException() { + super(); + } + + public ActionFailureException(String paramString) { + super(paramString); + } + + public ActionFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } +} From 8ca5c1f0586368976c3d27597b9df82a3bb491e6 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:26:15 +0800 Subject: [PATCH 12/40] ADD file via upload --- .../exception/NetworkFailureException.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/gtask/exception/NetworkFailureException.java diff --git a/src/gtask/exception/NetworkFailureException.java b/src/gtask/exception/NetworkFailureException.java new file mode 100644 index 0000000..b08cfb1 --- /dev/null +++ b/src/gtask/exception/NetworkFailureException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.exception; + +public class NetworkFailureException extends Exception { + private static final long serialVersionUID = 2107610287180234136L; + + public NetworkFailureException() { + super(); + } + + public NetworkFailureException(String paramString) { + super(paramString); + } + + public NetworkFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } +} From 1495c5438b510ce9f192dbeabf588b50e899ece0 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:26:44 +0800 Subject: [PATCH 13/40] ADD file via upload --- src/gtask/remote/GTaskASyncTask.java | 130 +++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/gtask/remote/GTaskASyncTask.java diff --git a/src/gtask/remote/GTaskASyncTask.java b/src/gtask/remote/GTaskASyncTask.java new file mode 100644 index 0000000..034caa7 --- /dev/null +++ b/src/gtask/remote/GTaskASyncTask.java @@ -0,0 +1,130 @@ + +/* +* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package net.micode.notes.gtask.remote; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesListActivity; +import net.micode.notes.ui.NotesPreferenceActivity; + +/*异步操作类,实现GTask的异步操作过程 +* 主要方法: +* private void showNotification(int tickerId, String content) 向用户提示当前同步的状态,是一个用于交互的方法 +* protected Integer doInBackground(Void... unused) 此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间 +* protected void onProgressUpdate(String... progress) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。 +* protected void onPostExecute(Integer result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI +*/ + +public class GTaskASyncTask extends AsyncTask { + +private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + +public interface OnCompleteListener { +void onComplete(); +} + +private Context mContext; + +private NotificationManager mNotifiManager; + +private GTaskManager mTaskManager; + +private OnCompleteListener mOnCompleteListener; + +public GTaskASyncTask(Context context, OnCompleteListener listener) { +mContext = context; +mOnCompleteListener = listener; +mNotifiManager = (NotificationManager) mContext +.getSystemService(Context.NOTIFICATION_SERVICE); +mTaskManager = GTaskManager.getInstance(); +} + +public void cancelSync() { +mTaskManager.cancelSync(); +} + +public void publishProgess(String message) {// 发布进度单位,系统将会调用onProgressUpdate()方法更新这些值 +publishProgress(new String[] { +message +}); +} + +private void showNotification(int tickerId, String content) { +Notification notification = new Notification(R.drawable.notification, mContext +.getString(tickerId), System.currentTimeMillis()); +notification.defaults = Notification.DEFAULT_LIGHTS;// 调用系统自带灯光 +notification.flags = Notification.FLAG_AUTO_CANCEL;// 点击清除按钮或点击通知后会自动消失 +PendingIntent pendingIntent;//一个描述了想要启动一个Activity、Broadcast或是Service的意图 +if (tickerId != R.string.ticker_success) { +pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, +NotesPreferenceActivity.class), 0);//如果同步不成功,那么从系统取得一个用于启动一个NotesPreferenceActivity的PendingIntent对象 + +} else { +pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, +NotesListActivity.class), 0);//如果同步成功,那么从系统取得一个用于启动一个NotesListActivity的PendingIntent对象 +} +notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, +pendingIntent); +mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);//通过NotificationManager对象的notify()方法来执行一个notification的消息 +} + +@Override +protected Integer doInBackground(Void... unused) { +publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity +.getSyncAccountName(mContext)));//利用getString,将把 NotesPreferenceActivity.getSyncAccountName(mContext))的字符串内容传进sync_progress_login中 +return mTaskManager.sync(mContext, this);//进行后台同步具体操作 +} + +@Override +protected void onProgressUpdate(String... progress) { +showNotification(R.string.ticker_syncing, progress[0]); +if (mContext instanceof GTaskSyncService) {//instanceof 判断mContext是否是GTaskSyncService的实例 +((GTaskSyncService) mContext).sendBroadcast(progress[0]); +} +} + +@Override +protected void onPostExecute(Integer result) {//用于在执行完后台任务后更新UI,显示结果 +if (result == GTaskManager.STATE_SUCCESS) { +showNotification(R.string.ticker_success, mContext.getString( +R.string.success_sync_account, mTaskManager.getSyncAccount())); +NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());//设置最新同步的时间 +} else if (result == GTaskManager.STATE_NETWORK_ERROR) { +showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); +} else if (result == GTaskManager.STATE_INTERNAL_ERROR) { +showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); +} else if (result == GTaskManager.STATE_SYNC_CANCELLED) { +showNotification(R.string.ticker_cancel, mContext +.getString(R.string.error_sync_cancelled)); +}//几种不同情况下的结果显示 +if (mOnCompleteListener != null) { +new Thread(new Runnable() { + +public void run() {//完成后的操作,使用onComplete()将所有值都重新初始化,相当于完成一次操作 +mOnCompleteListener.onComplete(); +} +}).start(); +} +} +} \ No newline at end of file From 3613d6694e56822103be89c988c9ee1236b3096a Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:26:52 +0800 Subject: [PATCH 14/40] ADD file via upload --- src/gtask/remote/GTaskClient.java | 657 ++++++++++++++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 src/gtask/remote/GTaskClient.java diff --git a/src/gtask/remote/GTaskClient.java b/src/gtask/remote/GTaskClient.java new file mode 100644 index 0000000..b309f12 --- /dev/null +++ b/src/gtask/remote/GTaskClient.java @@ -0,0 +1,657 @@ +/* +* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package net.micode.notes.gtask.remote; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.app.Activity; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.gtask.data.Node; +import net.micode.notes.gtask.data.Task; +import net.micode.notes.gtask.data.TaskList; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.gtask.exception.NetworkFailureException; +import net.micode.notes.tool.GTaskStringUtils; +import net.micode.notes.ui.NotesPreferenceActivity; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.cookie.Cookie; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.LinkedList; +import java.util.List; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +/* +* 主要功能:实现GTASK的登录操作,进行GTASK任务的创建,创建任务列表,从网络上获取任务和任务列表的内容 +* 主要使用类或技术:accountManager JSONObject HttpParams authToken Gid +*/ +public class GTaskClient { +private static final String TAG = GTaskClient.class.getSimpleName(); + +private static final String GTASK_URL = "https://mail.google.com/tasks/"; + +private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + +private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + +private static GTaskClient mInstance = null; + +private DefaultHttpClient mHttpClient; + +private String mGetUrl; + +private String mPostUrl; + +private long mClientVersion; + +private boolean mLoggedin; + +private long mLastLoginTime; + +private int mActionId; + +private Account mAccount; + +private JSONArray mUpdateArray; + +private GTaskClient() { +mHttpClient = null; +mGetUrl = GTASK_GET_URL; +mPostUrl = GTASK_POST_URL; +mClientVersion = -1; +mLoggedin = false; +mLastLoginTime = 0; +mActionId = 1; +mAccount = null; +mUpdateArray = null; +} +/*用来获取的实例化对象 +* 使用 getInstance() +* 返回mInstance这个实例化对象 +*/ +public static synchronized GTaskClient getInstance() { +if (mInstance == null) { +mInstance = new GTaskClient(); +} +return mInstance; +} +/*用来实现登录操作的函数,传入的参数是一个Activity +* 设置登录操作限制时间,如果超时则需要重新登录 +* 有两种登录方式,使用用户自己的URL登录或者使用谷歌官方的URL登录 +* 返回true或者false,即最后是否登陆成功 +*/ +public boolean login(Activity activity) { +// we suppose that the cookie would expire after 5 minutes +// then we need to re-login +//判断距离最后一次登录操作是否超过5分钟 +final long interval = 1000 * 60 * 5; +if (mLastLoginTime + interval < System.currentTimeMillis()) { +mLoggedin = false; +} + +// need to re-login after account switch 重新登录操作 +if (mLoggedin +&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity +.getSyncAccountName(activity))) { +mLoggedin = false; +} +//如果没超过时间,则不需要重新登录 +if (mLoggedin) { +Log.d(TAG, "already logged in"); +return true; +} + +mLastLoginTime = System.currentTimeMillis();//更新最后登录时间,改为系统当前的时间 +String authToken = loginGoogleAccount(activity, false);//判断是否登录到谷歌账户 +if (authToken == null) { +Log.e(TAG, "login google account failed"); +return false; +} + +// login with custom domain if necessary +//尝试使用用户自己的域名登录 +if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() +.endsWith("googlemail.com"))) { +StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); +int index = mAccount.name.indexOf('@') + 1; +String suffix = mAccount.name.substring(index); +url.append(suffix + "/"); +mGetUrl = url.toString() + "ig";//设置用户对应的getUrl +mPostUrl = url.toString() + "r/ig";//设置用户对应的postUrl + +if (tryToLoginGtask(activity, authToken)) { +mLoggedin = true; +} +} + +// try to login with google official url +//如果用户账户无法登录,则使用谷歌官方的URI进行登录 +if (!mLoggedin) { +mGetUrl = GTASK_GET_URL; +mPostUrl = GTASK_POST_URL; +if (!tryToLoginGtask(activity, authToken)) { +return false; +} +} + +mLoggedin = true; +return true; +} +/*具体实现登录谷歌账户的方法 +* 使用令牌机制 +* 使用AccountManager来管理注册账号 +* 返回值是账号的令牌 +*/ + +private String loginGoogleAccount(Activity activity, boolean invalidateToken) { +String authToken;//令牌,是登录操作保证安全性的一个方法 +AccountManager accountManager = AccountManager.get(activity);//AccountManager这个类给用户提供了集中注册账号的接口 +Account[] accounts = accountManager.getAccountsByType("com.google");//获取全部以com.google结尾的account + +if (accounts.length == 0) { +Log.e(TAG, "there is no available google account"); +return null; +} + +String accountName = NotesPreferenceActivity.getSyncAccountName(activity); +Account account = null; +//遍历获得的accounts信息,寻找已经记录过的账户信息 +for (Account a : accounts) { +if (a.name.equals(accountName)) { +account = a; +break; +} +} +if (account != null) { +mAccount = account; +} else { +Log.e(TAG, "unable to get an account with the same name in the settings"); +return null; +} + +// get the token now +//获取选中账号的令牌 +AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, +"goanna_mobile", null, activity, null, null); +try { +Bundle authTokenBundle = accountManagerFuture.getResult(); +authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); +//如果是invalidateToken,那么需要调用invalidateAuthToken(String, String)方法废除这个无效token +if (invalidateToken) { +accountManager.invalidateAuthToken("com.google", authToken); +loginGoogleAccount(activity, false); +} +} catch (Exception e) { +Log.e(TAG, "get auth token failed"); +authToken = null; +} + +return authToken; +} +//尝试登陆Gtask,这只是一个预先判断令牌是否是有效以及是否能登上GTask的方法,而不是具体实现登陆的方法 +private boolean tryToLoginGtask(Activity activity, String authToken) { +if (!loginGtask(authToken)) { +// maybe the auth token is out of date, now let's invalidate the +// token and try again +//删除过一个无效的authToken,申请一个新的后再次尝试登陆 +authToken = loginGoogleAccount(activity, true); +if (authToken == null) { +Log.e(TAG, "login google account failed"); +return false; +} + +if (!loginGtask(authToken)) { +Log.e(TAG, "login gtask failed"); +return false; +} +} +return true; +} +//实现登录GTask的具体操作 +private boolean loginGtask(String authToken) { +int timeoutConnection = 10000; +int timeoutSocket = 15000;//socket是一种通信连接实现数据的交换的端口 +HttpParams httpParameters = new BasicHttpParams();//实例化一个新的HTTP参数类 +HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);//设置连接超时时间 +HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);//设置端口超时时间 +mHttpClient = new DefaultHttpClient(httpParameters); +BasicCookieStore localBasicCookieStore = new BasicCookieStore();//设置本地cookie +mHttpClient.setCookieStore(localBasicCookieStore); +HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); + +// login gtask +try { +String loginUrl = mGetUrl + "?auth=" + authToken;//设置登录的url +HttpGet httpGet = new HttpGet(loginUrl);//通过登录的uri实例化网页上资源的查找 +HttpResponse response = null; +response = mHttpClient.execute(httpGet); + +// get the cookie now +//获取CookieStore里存放的cookie,看如果存有“GTL(不知道什么意思)”,则说明有验证成功的有效的cookie +List cookies = mHttpClient.getCookieStore().getCookies(); +boolean hasAuthCookie = false; +for (Cookie cookie : cookies) { +if (cookie.getName().contains("GTL")) { +hasAuthCookie = true; +} +} +if (!hasAuthCookie) { +Log.w(TAG, "it seems that there is no auth cookie"); +} + +// get the client version +//获取client的内容,具体操作是在返回的Content中截取从_setup(开始到)}中间的字符串内容,也就是gtask_url的内容 +String resString = getResponseContent(response.getEntity()); +String jsBegin = "_setup("; +String jsEnd = ")}"; +int begin = resString.indexOf(jsBegin); +int end = resString.lastIndexOf(jsEnd); +String jsString = null; +if (begin != -1 && end != -1 && begin < end) { +jsString = resString.substring(begin + jsBegin.length(), end); +} +JSONObject js = new JSONObject(jsString); +mClientVersion = js.getLong("v"); +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +return false; +} catch (Exception e) { +// simply catch all exceptions +Log.e(TAG, "httpget gtask_url failed"); +return false; +} + +return true; +} + +private int getActionId() { +return mActionId++; +} +/*实例化创建一个用于向网络传输数据的对象 +* 使用HttpPost类 +* 返回一个httpPost实例化对象,但里面还没有内容 +*/ +private HttpPost createHttpPost() { +HttpPost httpPost = new HttpPost(mPostUrl); +httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); +httpPost.setHeader("AT", "1"); +return httpPost; +} +/*通过URL获取响应后返回的数据,也就是网络上的数据和资源 +* 使用getContentEncoding()获取网络上的资源和数据 +* 返回值就是获取到的资源 +*/ +private String getResponseContent(HttpEntity entity) throws IOException { +String contentEncoding = null; +if (entity.getContentEncoding() != null) {//通过URL得到HttpEntity对象,如果不为空则使用getContent()方法创建一个流将数据从网络都过来 +contentEncoding = entity.getContentEncoding().getValue(); +Log.d(TAG, "encoding: " + contentEncoding); +} + +InputStream input = entity.getContent(); +if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { +input = new GZIPInputStream(entity.getContent()); +} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { +Inflater inflater = new Inflater(true); +input = new InflaterInputStream(entity.getContent(), inflater); +} + +try { +InputStreamReader isr = new InputStreamReader(input); +BufferedReader br = new BufferedReader(isr);//是一个包装类,它可以包装字符流,将字符流放入缓存里,先把字符读到缓存里,到缓存满了时候,再读入内存,是为了提供读的效率而设计的 +StringBuilder sb = new StringBuilder(); + +while (true) { +String buff = br.readLine(); +if (buff == null) { +return sb.toString(); +} +sb = sb.append(buff); +} +} finally { +input.close(); +} +} +/*通过JSON发送请求 +* 请求的具体内容在json的实例化对象js中然后传入 +* 利用UrlEncodedFormEntity entity和httpPost.setEntity(entity)方法把js中的内容放置到httpPost中 +* 执行请求后使用getResponseContent方法得到返回的数据和资源 +* 将资源再次放入json后返回 +*/ + +private JSONObject postRequest(JSONObject js) throws NetworkFailureException { +if (!mLoggedin) {//未登录 +Log.e(TAG, "please login first"); +throw new ActionFailureException("not logged in"); +} +//实例化一个httpPost的对象用来向服务器传输数据,在这里就是发送请求,而请求的内容在js里 +HttpPost httpPost = createHttpPost(); +try { +LinkedList list = new LinkedList(); +list.add(new BasicNameValuePair("r", js.toString())); +UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); +httpPost.setEntity(entity); + +// execute the post +//执行这个请求 +HttpResponse response = mHttpClient.execute(httpPost); +String jsString = getResponseContent(response.getEntity()); +return new JSONObject(jsString); + +} catch (ClientProtocolException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new NetworkFailureException("postRequest failed"); +} catch (IOException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new NetworkFailureException("postRequest failed"); +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("unable to convert response content to jsonobject"); +} catch (Exception e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("error occurs when posting request"); +} +} +/*创建单个任务 +* 传入参数是一个.gtask.data.Task包里Task类的对象 +* 利用json获取Task里的内容,并且创建相应的jsPost +* 利用postRequest得到任务的返回信息 +* 使用task.setGid设置task的new_ID +*/ + +public void createTask(Task task) throws NetworkFailureException { +commitUpdate(); +try { +JSONObject jsPost = new JSONObject(); +JSONArray actionList = new JSONArray(); + +// action_list +actionList.put(task.getCreateAction(getActionId())); +jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + +// client_version +jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + +// post +JSONObject jsResponse = postRequest(jsPost); +JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( +GTaskStringUtils.GTASK_JSON_RESULTS).get(0); +task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); + +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("create task: handing jsonobject failed"); +} +} +/* +* 创建一个任务列表,与createTask几乎一样,区别就是最后设置的是tasklist的gid +*/ +public void createTaskList(TaskList tasklist) throws NetworkFailureException { +commitUpdate(); +try { +JSONObject jsPost = new JSONObject(); +JSONArray actionList = new JSONArray(); + +// action_list +actionList.put(tasklist.getCreateAction(getActionId())); +jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + +// client version +jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + +// post +JSONObject jsResponse = postRequest(jsPost); +JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( +GTaskStringUtils.GTASK_JSON_RESULTS).get(0); +tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); + +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("create tasklist: handing jsonobject failed"); +} +} +/* +* 同步更新操作 +* 使用JSONObject进行数据存储,使用jsPost.put,Put的信息包括UpdateArray和ClientVersion +* 使用postRequest发送这个jspost,进行处理 +*/ +public void commitUpdate() throws NetworkFailureException { +if (mUpdateArray != null) { +try { +JSONObject jsPost = new JSONObject(); + +// action_list +jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); + +// client_version +jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + +postRequest(jsPost); +mUpdateArray = null; +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("commit update: handing jsonobject failed"); +} +} +} +/* +* 添加更新的事项 +* 调用commitUpdate()实现 +*/ +public void addUpdateNode(Node node) throws NetworkFailureException { +if (node != null) { +// too many update items may result in an error +// set max to 10 items +if (mUpdateArray != null && mUpdateArray.length() > 10) { +commitUpdate(); +} + +if (mUpdateArray == null) +mUpdateArray = new JSONArray(); +mUpdateArray.put(node.getUpdateAction(getActionId())); +} +} +/* +* 移动task,比如讲task移动到不同的task列表中去 +* 通过getGid获取task所属列表的gid +* 通过JSONObject.put(String name, Object value)函数设置移动后的task的相关属性值,从而达到移动的目的 +* 最后还是通过postRequest进行更新后的发送 +*/ + +public void moveTask(Task task, TaskList preParent, TaskList curParent) +throws NetworkFailureException { +commitUpdate(); +try { +JSONObject jsPost = new JSONObject(); +JSONArray actionList = new JSONArray(); +JSONObject action = new JSONObject(); + +// action_list +action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, +GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); +action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); +action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); +if (preParent == curParent && task.getPriorSibling() != null) { +// put prioring_sibing_id only if moving within the tasklist and +// it is not the first one +//设置优先级ID,只有当移动是发生在文件中 +action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); +} +action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); +action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); +if (preParent != curParent) { +// put the dest_list only if moving between tasklists +action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); +} +actionList.put(action); +//最后将ACTION_LIST加入到jsPost中 +jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + +// client_version +jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + +postRequest(jsPost); + +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("move task: handing jsonobject failed"); +} +} +/* +* 删除操作节点 +* 还是利用JSON +* 删除过后使用postRequest发送删除后的结果 +*/ +public void deleteNode(Node node) throws NetworkFailureException { +commitUpdate(); +try { +JSONObject jsPost = new JSONObject(); +JSONArray actionList = new JSONArray(); + +// action_list +node.setDeleted(true); +actionList.put(node.getUpdateAction(getActionId()));//这里会获取到删除操作的ID,加入到actionLiast中 +jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + +// client_version +jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + +postRequest(jsPost); +mUpdateArray = null; +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("delete node: handing jsonobject failed"); +} +} +/* +* 获取任务列表 +* 首先通过GetURI使用getResponseContent从网上获取数据 +* 然后筛选出"_setup("到)}的部分,并且从中获取GTASK_JSON_LISTS的内容返回 +*/ +public JSONArray getTaskLists() throws NetworkFailureException { +if (!mLoggedin) { +Log.e(TAG, "please login first"); +throw new ActionFailureException("not logged in"); +} + +try { +HttpGet httpGet = new HttpGet(mGetUrl); +HttpResponse response = null; +response = mHttpClient.execute(httpGet); + +// get the task list +//筛选工作,把筛选出的字符串放入jsString +String resString = getResponseContent(response.getEntity()); +String jsBegin = "_setup("; +String jsEnd = ")}"; +int begin = resString.indexOf(jsBegin); +int end = resString.lastIndexOf(jsEnd); +String jsString = null; +if (begin != -1 && end != -1 && begin < end) { +jsString = resString.substring(begin + jsBegin.length(), end); +} +JSONObject js = new JSONObject(jsString); +//获取GTASK_JSON_LISTS +return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); +} catch (ClientProtocolException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new NetworkFailureException("gettasklists: httpget failed"); +} catch (IOException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new NetworkFailureException("gettasklists: httpget failed"); +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("get task lists: handing jasonobject failed"); +} +} +/* +* 通过传入的TASKList的gid,从网络上获取相应属于这个任务列表的任务 +*/ +public JSONArray getTaskList(String listGid) throws NetworkFailureException { +commitUpdate(); +try { +JSONObject jsPost = new JSONObject(); +JSONArray actionList = new JSONArray(); +JSONObject action = new JSONObject(); + +// action_list +action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, +GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); +action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); +action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); +action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); +actionList.put(action); +jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + +// client_version +jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + +JSONObject jsResponse = postRequest(jsPost); +return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); +} catch (JSONException e) { +Log.e(TAG, e.toString()); +e.printStackTrace(); +throw new ActionFailureException("get task list: handing jsonobject failed"); +} +} + +public Account getSyncAccount() { +return mAccount; +} +//重置更新的内容 +public void resetUpdateArray() { +mUpdateArray = null; +} +} \ No newline at end of file From 7763ca8a3d96d2a022b5242e06c55563f3b557b2 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:27:00 +0800 Subject: [PATCH 15/40] ADD file via upload --- src/gtask/remote/GTaskManager.java | 825 +++++++++++++++++++++++++++++ 1 file changed, 825 insertions(+) create mode 100644 src/gtask/remote/GTaskManager.java diff --git a/src/gtask/remote/GTaskManager.java b/src/gtask/remote/GTaskManager.java new file mode 100644 index 0000000..1f118b7 --- /dev/null +++ b/src/gtask/remote/GTaskManager.java @@ -0,0 +1,825 @@ +/* +* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package net.micode.notes.gtask.remote; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.data.MetaData; +import net.micode.notes.gtask.data.Node; +import net.micode.notes.gtask.data.SqlNote; +import net.micode.notes.gtask.data.Task; +import net.micode.notes.gtask.data.TaskList; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.gtask.exception.NetworkFailureException; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +public class GTaskManager { +private static final String TAG = GTaskManager.class.getSimpleName(); + +public static final int STATE_SUCCESS = 0; + +public static final int STATE_NETWORK_ERROR = 1; + +public static final int STATE_INTERNAL_ERROR = 2; + +public static final int STATE_SYNC_IN_PROGRESS = 3; + +public static final int STATE_SYNC_CANCELLED = 4; + +private static GTaskManager mInstance = null; + +private Activity mActivity; + +private Context mContext; + +private ContentResolver mContentResolver; + +private boolean mSyncing; + +private boolean mCancelled; + +private HashMap mGTaskListHashMap; + +private HashMap mGTaskHashMap; + +private HashMap mMetaHashMap; + +private TaskList mMetaList; + +private HashSet mLocalDeleteIdMap; + +private HashMap mGidToNid; + +private HashMap mNidToGid; + +private GTaskManager() {//对象初始化函数 +mSyncing = false;//正在同步,flase代表未执行 +mCancelled = false;//全局标识,flase代表可以执行 +mGTaskListHashMap = new HashMap();//<>代表Java的泛型,就是创建一个用类型作为参数的类。 + +mGTaskHashMap = new HashMap(); +mMetaHashMap = new HashMap(); +mMetaList = null; +mLocalDeleteIdMap = new HashSet(); +mGidToNid = new HashMap();//GoogleID to NodeID +mNidToGid = new HashMap();//NodeID to GoogleID 通过hashmap散列表建立映射 +} +/** +* 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 +* 功能:类初始化函数 +*/ +public static synchronized GTaskManager getInstance() {//可能运行在多线程环境下,使用语言级同步--synchronized +if (mInstance == null) { +mInstance = new GTaskManager(); +} +return mInstance; +} +/** +* 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 +*/ +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");//创建日志文件(调试信息),debug +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();//getInstance即为创建一个实例,client--客户机 +client.resetUpdateArray();//JSONArray类型,reset即置为NULL + +// 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();//获取Google上的JSONtasklist转为本地TaskList + +// do content sync work +asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); +syncContent(); +} catch (NetworkFailureException e) {//分为两种异常,此类异常为网络异常 +Log.e(TAG, e.toString());//创建日志文件(调试信息),error +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; +} +/** +*功能:初始化GtaskList,获取Google上的JSONtasklist转为本地TaskList。 +*获得的数据存储在mMetaList,mGTaskListHashMap,mGTaskHashMap +*/ + +private void initGTaskList() throws NetworkFailureException { +if (mCancelled) +return; +GTaskClient client = GTaskClient.getInstance();//getInstance即为创建一个实例,client应指远端客户机 +try { +//Json对象是Name Value对(即子元素)的无序集合,相当于一个Map对象。JsonObject类是bantouyan-json库对Json对象的抽象,提供操纵Json对象的各种方法。 +    //其格式为{"key1":value1,"key2",value2....};key 必须是字符串。 +    //因为ajax请求不刷新页面,但配合js可以实现局部刷新,因此json常常被用来作为异步请求的返回对象使用。 + +JSONArray jsTaskLists = client.getTaskLists(); + +// init meta list first +mMetaList = null;//TaskList类型 +for (int i = 0; i < jsTaskLists.length(); i++) { +JSONObject object = jsTaskLists.getJSONObject(i);//JSONObject与JSONArray一个为对象,一个为数组。此处取出单个JASONObject +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();//MetaList意为元表,Tasklist类型,此处为初始化 +mMetaList.setContentByRemoteJSON(object);//将JSON中部分数据复制到自己定义的对象中相对应的数据:name->mname + +// 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();//继承自Node +metaData.setContentByRemoteJSON(object); +if (metaData.isWorthSaving()) {//if not worth to save,metadata将不加入mMetaList +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);//通过getString函数传入本地某个标志数据的名称,获取其在远端的名称。 +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();//继承自Node +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;//GoogleID +Node node;//Node包含Sync_Action的不同类型 + +mLocalDeleteIdMap.clear();//HashSet类型 + +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));//通过hashmap建立联系 +mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);//通过hashmap建立联系 +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> iter = mGTaskHashMap.entrySet().iterator();//Iterator迭代器 +while (iter.hasNext()) { +Map.Entry 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> iter = mGTaskListHashMap.entrySet().iterator(); +while (iter.hasNext()) { +Map.Entry 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(); +} +//功能:syncType分类,addLocalNode,addRemoteNode,deleteNode,updateLocalNode,updateRemoteNode +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"); +} +} +//功能:本地增加Node +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); +} +//功能:update本地node +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); +} +/** +* 功能:远程增加Node +* 需要updateRemoteMeta +*/ +private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { +if (mCancelled) { +return; +} + +SqlNote sqlNote = new SqlNote(mContext, c);//从本地mContext中获取内容 +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);//在本地生成的GTaskList中增加子结点 +//登录远程服务器,创建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元素 +Iterator> iter = mGTaskListHashMap.entrySet().iterator(); +while (iter.hasNext()) { +Map.Entry 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 //创建id间的映射 +mGidToNid.put(n.getGid(), sqlNote.getId()); +mNidToGid.put(sqlNote.getId(), n.getGid()); +} +//功能:更新远端的Node,包含meta更新(updateRemoteMeta) +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(); +//preParentList为通过node获取的父节点列表 + +String curParentGid = mNidToGid.get(sqlNote.getParentId()); +//curParentGid为通过光标在数据库中找到sqlNote的mParentId,再通过mNidToGid由long类型转为String类型的Gid +if (curParentGid == null) { +Log.e(TAG, "cannot find task's parent tasklist"); +throw new ActionFailureException("cannot update remote task"); +} +TaskList curParentList = mGTaskListHashMap.get(curParentGid); +//通过HashMap找到对应Gid的TaskList + +if (preParentList != curParentList) { +preParentList.removeChildTask(task); +curParentList.addChildTask(task); +GTaskClient.getInstance().moveTask(task, preParentList, curParentList); +} +} + +// clear local modified flag +sqlNote.resetLocalModified(); +//commit到本地数据库 +sqlNote.commit(true); +} +//功能:升级远程meta。 meta---元数据----计算机文件系统管理数据---管理数据的数据 +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); +} +} +} +//功能:刷新本地,给sync的ID对应上最后更改过的对象 +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");//query语句:五个参数,NoteColumns.TYPE + " DESC"-----为按类型递减顺序返回查询结果。new String[] {String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)}------为选择参数。"(type<>? AND parent_id<>?)"-------指明返回行过滤器。SqlNote.PROJECTION_NOTE--------应返回的数据列的名字。Notes.CONTENT_NOTE_URI--------contentProvider包含所有数据集所对应的uri + +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();//在ContentValues中创建键值对。准备通过contentResolver写入数据 +values.put(NoteColumns.SYNC_ID, node.getLastModified()); +mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,//进行批量更改,选择参数为NULL,应该可以用insert替换,参数分别为表名和需要更新的value对象。 +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; +} +} +} +//功能:获取同步账号,mAccount.name + +public String getSyncAccount() { +return GTaskClient.getInstance().getSyncAccount().name; +} +//功能:取消同步,置mCancelled为true +public void cancelSync() { +mCancelled = true; +} +} \ No newline at end of file From a904924ade6b496d8eea6dade0075c3a0e1d4a52 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:27:09 +0800 Subject: [PATCH 16/40] ADD file via upload --- src/gtask/remote/GTaskSyncService.java | 146 +++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/gtask/remote/GTaskSyncService.java diff --git a/src/gtask/remote/GTaskSyncService.java b/src/gtask/remote/GTaskSyncService.java new file mode 100644 index 0000000..51011ad --- /dev/null +++ b/src/gtask/remote/GTaskSyncService.java @@ -0,0 +1,146 @@ +/* +* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package net.micode.notes.gtask.remote; + +import android.app.Activity; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +/* +* Service是在一段不定的时间运行在后台,不和用户交互的应用组件 +* 主要方法: +* private void startSync() 启动一个同步工作 +* private void cancelSync() 取消同步 +* public void onCreate() +* public int onStartCommand(Intent intent, int flags, int startId) service生命周期的组成部分,相当于重启service(比如在被暂停之后),而不是创建一个新的service +* public void onLowMemory() 在没有内存的情况下如果存在service则结束掉这的service +* public IBinder onBind() +* public void sendBroadcast(String msg) 发送同步的相关通知 +* public static void startSync(Activity activity) +* public static void cancelSync(Context context) +* public static boolean isSyncing() 判读是否在进行同步 +* public static String getProgressString() 获取当前进度的信息 +*/ + +public class GTaskSyncService extends Service { +public final static String ACTION_STRING_NAME = "sync_action_type"; + +public final static int ACTION_START_SYNC = 0; + +public final static int ACTION_CANCEL_SYNC = 1; + +public final static int ACTION_INVALID = 2; + +public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + +public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + +public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + +private static GTaskASyncTask mSyncTask = null; + +private static String mSyncProgress = ""; + +//开始一个同步的工作 +private void startSync() { +if (mSyncTask == null) { +mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { +public void onComplete() { +mSyncTask = null; +sendBroadcast(""); +stopSelf(); +} +}); +sendBroadcast(""); +mSyncTask.execute();//这个函数让任务是以单线程队列方式或线程池队列方式运行 +} +} + +private void cancelSync() { +if (mSyncTask != null) { +mSyncTask.cancelSync(); +} +} + +@Override +public void onCreate() {//初始化一个service +mSyncTask = null; +} + +@Override +public int onStartCommand(Intent intent, int flags, int startId) { +Bundle bundle = intent.getExtras(); +if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { +switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { +//两种情况,开始同步或者取消同步 +case ACTION_START_SYNC: +startSync(); +break; +case ACTION_CANCEL_SYNC: +cancelSync(); +break; +default: +break; +} +return START_STICKY;//等待新的intent来是这个service继续运行 +} +return super.onStartCommand(intent, flags, startId); +} + +@Override +public void onLowMemory() { +if (mSyncTask != null) { +mSyncTask.cancelSync(); +} +} + +public IBinder onBind(Intent intent) { +return null; +} + +public void sendBroadcast(String msg) { +mSyncProgress = msg; +Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);//创建一个新的Intent +intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);//附加INTENT中的相应参数的值 +intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); +sendBroadcast(intent);//发送这个通知 +} + +public static void startSync(Activity activity) {//执行一个service,service的内容里的同步动作就是开始同步 + +GTaskManager.getInstance().setActivityContext(activity); +Intent intent = new Intent(activity, GTaskSyncService.class); +intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); +activity.startService(intent); +} + +public static void cancelSync(Context context) {//执行一个service,service的内容里的同步动作就是取消同步 +Intent intent = new Intent(context, GTaskSyncService.class); +intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); +context.startService(intent); +} + +public static boolean isSyncing() { +return mSyncTask != null; +} + +public static String getProgressString() { +return mSyncProgress; +} +} \ No newline at end of file From 31caf7f60576f7b2f319b507166682b7fe053c64 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:27:49 +0800 Subject: [PATCH 17/40] ADD file via upload --- src/model/Note.java | 253 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 src/model/Note.java diff --git a/src/model/Note.java b/src/model/Note.java new file mode 100644 index 0000000..6706cf6 --- /dev/null +++ b/src/model/Note.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.model; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.net.Uri; +import android.os.RemoteException; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.CallNote; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.Notes.TextNote; + +import java.util.ArrayList; + + +public class Note { + private ContentValues mNoteDiffValues; + private NoteData mNoteData; + private static final String TAG = "Note"; + /** + * Create a new note id for adding a new note to databases + */ + public static synchronized long getNewNoteId(Context context, long folderId) { + // Create a new note in the database + ContentValues values = new ContentValues(); + long createdTime = System.currentTimeMillis(); + values.put(NoteColumns.CREATED_DATE, createdTime); + values.put(NoteColumns.MODIFIED_DATE, createdTime); + values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + values.put(NoteColumns.LOCAL_MODIFIED, 1); + values.put(NoteColumns.PARENT_ID, folderId); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); + + long noteId = 0; + try { + noteId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + Log.e(TAG, "Get note id error :" + e.toString()); + noteId = 0; + } + if (noteId == -1) { + throw new IllegalStateException("Wrong note id:" + noteId); + } + return noteId; + } + + public Note() { + mNoteDiffValues = new ContentValues(); + mNoteData = new NoteData(); + } + + public void setNoteValue(String key, String value) { + mNoteDiffValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + + public void setTextData(String key, String value) { + mNoteData.setTextData(key, value); + } + + public void setTextDataId(long id) { + mNoteData.setTextDataId(id); + } + + public long getTextDataId() { + return mNoteData.mTextDataId; + } + + public void setCallDataId(long id) { + mNoteData.setCallDataId(id); + } + + public void setCallData(String key, String value) { + mNoteData.setCallData(key, value); + } + + public boolean isLocalModified() { + return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); + } + + public boolean syncNote(Context context, long noteId) { + if (noteId <= 0) { + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + + if (!isLocalModified()) { + return true; + } + + /** + * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and + * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the + * note data info + */ + if (context.getContentResolver().update( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, + null) == 0) { + Log.e(TAG, "Update note error, should not happen"); + // Do not return, fall through + } + mNoteDiffValues.clear(); + + if (mNoteData.isLocalModified() + && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { + return false; + } + + return true; + } + + private class NoteData { + private long mTextDataId; + + private ContentValues mTextDataValues; + + private long mCallDataId; + + private ContentValues mCallDataValues; + + private static final String TAG = "NoteData"; + + public NoteData() { + mTextDataValues = new ContentValues(); + mCallDataValues = new ContentValues(); + mTextDataId = 0; + mCallDataId = 0; + } + + boolean isLocalModified() { + return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; + } + + void setTextDataId(long id) { + if(id <= 0) { + throw new IllegalArgumentException("Text data id should larger than 0"); + } + mTextDataId = id; + } + + void setCallDataId(long id) { + if (id <= 0) { + throw new IllegalArgumentException("Call data id should larger than 0"); + } + mCallDataId = id; + } + + void setCallData(String key, String value) { + mCallDataValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + + void setTextData(String key, String value) { + mTextDataValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + + Uri pushIntoContentResolver(Context context, long noteId) { + /** + * Check for safety + */ + if (noteId <= 0) { + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + + ArrayList operationList = new ArrayList(); + ContentProviderOperation.Builder builder = null; + + if(mTextDataValues.size() > 0) { + mTextDataValues.put(DataColumns.NOTE_ID, noteId); + if (mTextDataId == 0) { + mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mTextDataValues); + try { + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + Log.e(TAG, "Insert new text data fail with noteId" + noteId); + mTextDataValues.clear(); + return null; + } + } else { + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mTextDataId)); + builder.withValues(mTextDataValues); + operationList.add(builder.build()); + } + mTextDataValues.clear(); + } + + if(mCallDataValues.size() > 0) { + mCallDataValues.put(DataColumns.NOTE_ID, noteId); + if (mCallDataId == 0) { + mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mCallDataValues); + try { + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + Log.e(TAG, "Insert new call data fail with noteId" + noteId); + mCallDataValues.clear(); + return null; + } + } else { + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mCallDataId)); + builder.withValues(mCallDataValues); + operationList.add(builder.build()); + } + mCallDataValues.clear(); + } + + if (operationList.size() > 0) { + try { + ContentProviderResult[] results = context.getContentResolver().applyBatch( + Notes.AUTHORITY, operationList); + return (results == null || results.length == 0 || results[0] == null) ? null + : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } + } + return null; + } + } +} From 6b35e39232b6282c52c6ca60eb54ccc46ed74da8 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:28:01 +0800 Subject: [PATCH 18/40] ADD file via upload --- src/model/WorkingNote.java | 368 +++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 src/model/WorkingNote.java diff --git a/src/model/WorkingNote.java b/src/model/WorkingNote.java new file mode 100644 index 0000000..be081e4 --- /dev/null +++ b/src/model/WorkingNote.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.model; + +import android.appwidget.AppWidgetManager; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.CallNote; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.Notes.TextNote; +import net.micode.notes.tool.ResourceParser.NoteBgResources; + + +public class WorkingNote { + // Note for the working note + private Note mNote; + // Note Id + private long mNoteId; + // Note content + private String mContent; + // Note mode + private int mMode; + + private long mAlertDate; + + private long mModifiedDate; + + private int mBgColorId; + + private int mWidgetId; + + private int mWidgetType; + + private long mFolderId; + + private Context mContext; + + private static final String TAG = "WorkingNote"; + + private boolean mIsDeleted; + + private NoteSettingChangedListener mNoteSettingStatusListener; + + public static final String[] DATA_PROJECTION = new String[] { + DataColumns.ID, + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + + public static final String[] NOTE_PROJECTION = new String[] { + NoteColumns.PARENT_ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + NoteColumns.MODIFIED_DATE + }; + + private static final int DATA_ID_COLUMN = 0; + + private static final int DATA_CONTENT_COLUMN = 1; + + private static final int DATA_MIME_TYPE_COLUMN = 2; + + private static final int DATA_MODE_COLUMN = 3; + + private static final int NOTE_PARENT_ID_COLUMN = 0; + + private static final int NOTE_ALERTED_DATE_COLUMN = 1; + + private static final int NOTE_BG_COLOR_ID_COLUMN = 2; + + private static final int NOTE_WIDGET_ID_COLUMN = 3; + + private static final int NOTE_WIDGET_TYPE_COLUMN = 4; + + private static final int NOTE_MODIFIED_DATE_COLUMN = 5; + + // New note construct + private WorkingNote(Context context, long folderId) { + mContext = context; + mAlertDate = 0; + mModifiedDate = System.currentTimeMillis(); + mFolderId = folderId; + mNote = new Note(); + mNoteId = 0; + mIsDeleted = false; + mMode = 0; + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + } + + // Existing note construct + private WorkingNote(Context context, long noteId, long folderId) { + mContext = context; + mNoteId = noteId; + mFolderId = folderId; + mIsDeleted = false; + mNote = new Note(); + loadNote(); + } + + private void loadNote() { + Cursor cursor = mContext.getContentResolver().query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, + null, null); + + if (cursor != null) { + if (cursor.moveToFirst()) { + mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); + mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); + mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); + mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); + mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); + } + cursor.close(); + } else { + Log.e(TAG, "No note with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note with id " + mNoteId); + } + loadNoteData(); + } + + private void loadNoteData() { + Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, + DataColumns.NOTE_ID + "=?", new String[] { + String.valueOf(mNoteId) + }, null); + + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + String type = cursor.getString(DATA_MIME_TYPE_COLUMN); + if (DataConstants.NOTE.equals(type)) { + mContent = cursor.getString(DATA_CONTENT_COLUMN); + mMode = cursor.getInt(DATA_MODE_COLUMN); + mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); + } else if (DataConstants.CALL_NOTE.equals(type)) { + mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); + } else { + Log.d(TAG, "Wrong note type with type:" + type); + } + } while (cursor.moveToNext()); + } + cursor.close(); + } else { + Log.e(TAG, "No data with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); + } + } + + public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, + int widgetType, int defaultBgColorId) { + WorkingNote note = new WorkingNote(context, folderId); + note.setBgColorId(defaultBgColorId); + note.setWidgetId(widgetId); + note.setWidgetType(widgetType); + return note; + } + + public static WorkingNote load(Context context, long id) { + return new WorkingNote(context, id, 0); + } + + public synchronized boolean saveNote() { + if (isWorthSaving()) { + if (!existInDatabase()) { + if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { + Log.e(TAG, "Create new note fail with id:" + mNoteId); + return false; + } + } + + mNote.syncNote(mContext, mNoteId); + + /** + * Update widget content if there exist any widget of this note + */ + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE + && mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onWidgetChanged(); + } + return true; + } else { + return false; + } + } + + public boolean existInDatabase() { + return mNoteId > 0; + } + + private boolean isWorthSaving() { + if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) + || (existInDatabase() && !mNote.isLocalModified())) { + return false; + } else { + return true; + } + } + + public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { + mNoteSettingStatusListener = l; + } + + public void setAlertDate(long date, boolean set) { + if (date != mAlertDate) { + mAlertDate = date; + mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); + } + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onClockAlertChanged(date, set); + } + } + + public void markDeleted(boolean mark) { + mIsDeleted = mark; + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onWidgetChanged(); + } + } + + public void setBgColorId(int id) { + if (id != mBgColorId) { + mBgColorId = id; + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onBackgroundColorChanged(); + } + mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); + } + } + + public void setCheckListMode(int mode) { + if (mMode != mode) { + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); + } + mMode = mode; + mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); + } + } + + public void setWidgetType(int type) { + if (type != mWidgetType) { + mWidgetType = type; + mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); + } + } + + public void setWidgetId(int id) { + if (id != mWidgetId) { + mWidgetId = id; + mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); + } + } + + public void setWorkingText(String text) { + if (!TextUtils.equals(mContent, text)) { + mContent = text; + mNote.setTextData(DataColumns.CONTENT, mContent); + } + } + + public void convertToCallNote(String phoneNumber, long callDate) { + mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); + mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); + mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); + } + + public boolean hasClockAlert() { + return (mAlertDate > 0 ? true : false); + } + + public String getContent() { + return mContent; + } + + public long getAlertDate() { + return mAlertDate; + } + + public long getModifiedDate() { + return mModifiedDate; + } + + public int getBgColorResId() { + return NoteBgResources.getNoteBgResource(mBgColorId); + } + + public int getBgColorId() { + return mBgColorId; + } + + public int getTitleBgResId() { + return NoteBgResources.getNoteTitleBgResource(mBgColorId); + } + + public int getCheckListMode() { + return mMode; + } + + public long getNoteId() { + return mNoteId; + } + + public long getFolderId() { + return mFolderId; + } + + public int getWidgetId() { + return mWidgetId; + } + + public int getWidgetType() { + return mWidgetType; + } + + public interface NoteSettingChangedListener { + /** + * Called when the background color of current note has just changed + */ + void onBackgroundColorChanged(); + + /** + * Called when user set clock + */ + void onClockAlertChanged(long date, boolean set); + + /** + * Call when user create note from widget + */ + void onWidgetChanged(); + + /** + * Call when switch between check list mode and normal mode + * @param oldMode is previous mode before change + * @param newMode is new mode + */ + void onCheckListModeChanged(int oldMode, int newMode); + } +} From 3bdfef2f815cdb4582036a57f1901c48db7b63f8 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:28:20 +0800 Subject: [PATCH 19/40] ADD file via upload --- src/tool/BackupUtils.java | 342 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 src/tool/BackupUtils.java diff --git a/src/tool/BackupUtils.java b/src/tool/BackupUtils.java new file mode 100644 index 0000000..d0a5632 --- /dev/null +++ b/src/tool/BackupUtils.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; + + +public class BackupUtils { + private static final String TAG = "BackupUtils"; + // Singleton stuff + private static BackupUtils sInstance; + + public static synchronized BackupUtils getInstance(Context context) { + if (sInstance == null) { + sInstance = new BackupUtils(context); + } + return sInstance; + } + + /** + * Following states are signs to represents backup or restore + * status + */ + // Currently, the sdcard is not mounted + public static final int STATE_SD_CARD_UNMOUONTED = 0; + // The backup file not exist + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; + // The data is not well formated, may be changed by other programs + public static final int STATE_DATA_DESTROIED = 2; + // Some run-time exception which causes restore or backup fails + public static final int STATE_SYSTEM_ERROR = 3; + // Backup or restore success + public static final int STATE_SUCCESS = 4; + + private TextExport mTextExport; + + private BackupUtils(Context context) { + mTextExport = new TextExport(context); + } + + private static boolean externalStorageAvailable() { + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + public int exportToText() { + return mTextExport.exportToText(); + } + + public String getExportedTextFileName() { + return mTextExport.mFileName; + } + + public String getExportedTextFileDir() { + return mTextExport.mFileDirectory; + } + + private static class TextExport { + private static final String[] NOTE_PROJECTION = { + NoteColumns.ID, + NoteColumns.MODIFIED_DATE, + NoteColumns.SNIPPET, + NoteColumns.TYPE + }; + + private static final int NOTE_COLUMN_ID = 0; + + private static final int NOTE_COLUMN_MODIFIED_DATE = 1; + + private static final int NOTE_COLUMN_SNIPPET = 2; + + private static final String[] DATA_PROJECTION = { + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + + private static final int DATA_COLUMN_CONTENT = 0; + + private static final int DATA_COLUMN_MIME_TYPE = 1; + + private static final int DATA_COLUMN_CALL_DATE = 2; + + private static final int DATA_COLUMN_PHONE_NUMBER = 4; + + private final String [] TEXT_FORMAT; + private static final int FORMAT_FOLDER_NAME = 0; + private static final int FORMAT_NOTE_DATE = 1; + private static final int FORMAT_NOTE_CONTENT = 2; + + private Context mContext; + private String mFileName; + private String mFileDirectory; + + public TextExport(Context context) { + TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); + mContext = context; + mFileName = ""; + mFileDirectory = ""; + } + + private String getFormat(int id) { + return TEXT_FORMAT[id]; + } + + /** + * Export the folder identified by folder id to text + */ + private void exportFolderToText(String folderId, PrintStream ps) { + // Query notes belong to this folder + Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { + folderId + }, null); + + if (notesCursor != null) { + if (notesCursor.moveToFirst()) { + do { + // Print note's last modified date + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + String noteId = notesCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); + } while (notesCursor.moveToNext()); + } + notesCursor.close(); + } + } + + /** + * Export note identified by id to a print stream + */ + private void exportNoteToText(String noteId, PrintStream ps) { + Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { + noteId + }, null); + + if (dataCursor != null) { + if (dataCursor.moveToFirst()) { + do { + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); + if (DataConstants.CALL_NOTE.equals(mimeType)) { + // Print phone number + String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); + long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); + String location = dataCursor.getString(DATA_COLUMN_CONTENT); + + if (!TextUtils.isEmpty(phoneNumber)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + phoneNumber)); + } + // Print call date + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat + .format(mContext.getString(R.string.format_datetime_mdhm), + callDate))); + // Print call attachment location + if (!TextUtils.isEmpty(location)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + location)); + } + } else if (DataConstants.NOTE.equals(mimeType)) { + String content = dataCursor.getString(DATA_COLUMN_CONTENT); + if (!TextUtils.isEmpty(content)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + content)); + } + } + } while (dataCursor.moveToNext()); + } + dataCursor.close(); + } + // print a line separator between note + try { + ps.write(new byte[] { + Character.LINE_SEPARATOR, Character.LETTER_NUMBER + }); + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + } + + /** + * Note will be exported as text which is user readable + */ + public int exportToText() { + if (!externalStorageAvailable()) { + Log.d(TAG, "Media was not mounted"); + return STATE_SD_CARD_UNMOUONTED; + } + + PrintStream ps = getExportToTextPrintStream(); + if (ps == null) { + Log.e(TAG, "get print stream error"); + return STATE_SYSTEM_ERROR; + } + // First export folder and its notes + Cursor folderCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); + + if (folderCursor != null) { + if (folderCursor.moveToFirst()) { + do { + // Print folder's name + String folderName = ""; + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + folderName = mContext.getString(R.string.call_record_folder_name); + } else { + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); + } + if (!TextUtils.isEmpty(folderName)) { + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + } + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + exportFolderToText(folderId, ps); + } while (folderCursor.moveToNext()); + } + folderCursor.close(); + } + + // Export notes in root's folder + Cursor noteCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + + "=0", null, null); + + if (noteCursor != null) { + if (noteCursor.moveToFirst()) { + do { + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); + } while (noteCursor.moveToNext()); + } + noteCursor.close(); + } + ps.close(); + + return STATE_SUCCESS; + } + + /** + * Get a print stream pointed to the file {@generateExportedTextFile} + */ + private PrintStream getExportToTextPrintStream() { + File file = generateFileMountedOnSDcard(mContext, R.string.file_path, + R.string.file_name_txt_format); + if (file == null) { + Log.e(TAG, "create file to exported failed"); + return null; + } + mFileName = file.getName(); + mFileDirectory = mContext.getString(R.string.file_path); + PrintStream ps = null; + try { + FileOutputStream fos = new FileOutputStream(file); + ps = new PrintStream(fos); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } catch (NullPointerException e) { + e.printStackTrace(); + return null; + } + return ps; + } + } + + /** + * Generate the text file to store imported data + */ + private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + StringBuilder sb = new StringBuilder(); + sb.append(Environment.getExternalStorageDirectory()); + sb.append(context.getString(filePathResId)); + File filedir = new File(sb.toString()); + sb.append(context.getString( + fileNameFormatResId, + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); + File file = new File(sb.toString()); + + try { + if (!filedir.exists()) { + filedir.mkdir(); + } + if (!file.exists()) { + file.createNewFile(); + } + return file; + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } +} From 478d4d8859c77b2aa3807af309b16d250d40c5e1 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:28:30 +0800 Subject: [PATCH 20/40] ADD file via upload --- src/tool/DataUtils.java | 366 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 src/tool/DataUtils.java diff --git a/src/tool/DataUtils.java b/src/tool/DataUtils.java new file mode 100644 index 0000000..6b91900 --- /dev/null +++ b/src/tool/DataUtils.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.os.RemoteException; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.CallNote; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; + +import java.util.ArrayList; +import java.util.HashSet; + + +public class DataUtils { + public static final String TAG = "DataUtils"; + + /** + * 批量删除笔记方法 + * + * @param resolver ContentResolver对象,用于访问应用数据 + * @param ids 要删除的笔记的ID集合 + * @return 删除是否成功,成功返回true,否则返回false + */ + public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { + // 检查输入的ids是否为null + if (ids == null) { + Log.d(TAG, "ids 为 null"); + return true; + } + // 检查ids集合是否为空 + if (ids.size() == 0) { + Log.d(TAG, "哈希集合中没有 id"); + return true; + } + + // 准备ContentProviderOperation集合,用于批量处理删除操作 + ArrayList operationList = new ArrayList(); + for (long id : ids) { + // 检查是否为系统文件夹根目录,如果是则跳过 + if (id == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "不要删除系统文件夹根目录"); + continue; + } + // 创建删除笔记的ContentProviderOperation.Builder对象 + ContentProviderOperation.Builder builder = ContentProviderOperation + .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); + // 将删除操作添加到集合中 + operationList.add(builder.build()); + } + try { + // 应用批量操作 + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); + // 检查删除是否成功 + if (results == null || results.length == 0 || results[0] == null) { + Log.d(TAG, "删除笔记失败,ids:" + ids.toString()); + return false; + } + return true; + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + } + return false; + } + + /** + * 将笔记移动到指定文件夹方法 + * + * @param resolver ContentResolver对象,用于访问应用数据 + * @param id 要移动的笔记ID + * @param srcFolderId 源文件夹ID + * @param desFolderId 目标文件夹ID + */ + public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { + // 准备ContentValues对象,用于更新笔记的父文件夹信息 + ContentValues values = new ContentValues(); + values.put(NoteColumns.PARENT_ID, desFolderId); + values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); + values.put(NoteColumns.LOCAL_MODIFIED, 1); + // 执行更新操作 + resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); + } +} + + + /** + * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + */ + /** + * 获取用户文件夹的数量 + * + * @param resolver ContentResolver对象,用于访问应用数据 + * @return 用户文件夹的数量 + */ + public static int getUserFolderCount(ContentResolver resolver) { + Cursor cursor = resolver.query( + Notes.CONTENT_NOTE_URI, + new String[]{"COUNT(*)"}, + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[]{String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, + null + ); + + int count = 0; + if (cursor != null) { + if (cursor.moveToFirst()) { + try { + count = cursor.getInt(0); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "获取文件夹数量失败:" + e.toString()); + } finally { + cursor.close(); + } + } + } + return count; + } + + /** + * 检查笔记是否在数据库中可见 + * + * @param resolver ContentResolver对象,用于访问应用数据 + * @param noteId 笔记的ID + * @param type 笔记的类型 + * @return 笔记是否在数据库中可见 + */ + public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { + Cursor cursor = resolver.query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, + new String[]{String.valueOf(type)}, + null + ); + + boolean exist = false; + if (cursor != null) { + if (cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist; + } + + /** + * 检查笔记是否存在于数据库中 + * + * @param resolver ContentResolver对象,用于访问应用数据 + * @param noteId 笔记的ID + * @return 笔记是否存在于数据库中 + */ + public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { + Cursor cursor = resolver.query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, + null, + null, + null + ); + + boolean exist = false; + if (cursor != null) { + if (cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist; + } + + + /** + * 判断给定的数据ID是否存在于数据数据库中。 + * + * @param resolver 用于访问ContentProvider的ContentResolver对象。 + * @param dataId 要检查的数据ID。 + * @return 如果数据存在则返回true,否则返回false。 + */ + public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { + // 通过ContentUris构建带有指定数据ID的URI,并查询相应的数据。 + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), + null, null, null, null); + + boolean exist = false; + if (cursor != null) { + // 如果查询到的数据行数大于0,则说明数据存在。 + if (cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist; + } + + /** + * 检查给定名称的可见文件夹是否存在。 + * + * @param resolver 用于访问ContentProvider的ContentResolver对象。 + * @param name 要检查的文件夹名称。 + * @return 如果文件夹存在则返回true,否则返回false。 + */ + public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { + // 查询所有类型为文件夹、非在垃圾桶中的,并且具有指定名称的便签。 + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", + new String[] { name }, null); + + boolean exist = false; + if(cursor != null) { + // 如果查询到的数据行数大于0,则说明文件夹存在。 + if(cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist; + } + + + /** + * 获取文件夹笔记小部件信息。 + * + * @param resolver 用于访问ContentProvider的ContentResolver对象。 + * @param folderId 要检索小部件信息的文件夹ID。 + * @return 包含小部件属性的HashSet集合,如果没有数据则返回null。 + */ + public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { + Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, + new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, + NoteColumns.PARENT_ID + "=?", + new String[] { String.valueOf(folderId) }, + null); + + HashSet set = null; + if (c != null) { + if (c.moveToFirst()) { + set = new HashSet(); + do { + try { + AppWidgetAttribute widget = new AppWidgetAttribute(); + widget.widgetId = c.getInt(0); + widget.widgetType = c.getInt(1); + set.add(widget); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, e.toString()); + } + } while (c.moveToNext()); + } + c.close(); + } + return set; + } + + /** + * 通过笔记ID获取通话号码。 + * + * @param resolver 用于访问ContentProvider的ContentResolver对象。 + * @param noteId 要检索通话号码的笔记ID。 + * @return 笔记相关的通话号码,如果没有找到则返回空字符串。 + */ + public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { + Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, + new String [] { CallNote.PHONE_NUMBER }, + CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", + new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, + null); + + if (cursor != null && cursor.moveToFirst()) { + try { + return cursor.getString(0); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "获取通话号码失败 " + e.toString()); + } finally { + cursor.close(); + } + } + return ""; + } + + public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { + // 查询通话笔记的数据 + Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, + new String[]{CallNote.NOTE_ID}, + CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" + + CallNote.PHONE_NUMBER + ",?)", + new String[]{String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber}, + null); + + if (cursor != null) { + if (cursor.moveToFirst()) { + try { + // 获取通话笔记的ID并返回 + return cursor.getLong(0); + } catch (IndexOutOfBoundsException e) { + // 处理异常,记录错误日志 + Log.e(TAG, "Get call note id fails " + e.toString()); + } + } + cursor.close(); + } + // 如果没有匹配的通话笔记,则返回0 + return 0; + } + + public static String getSnippetById(ContentResolver resolver, long noteId) { + // 根据通话笔记的ID查询相应的摘要信息 + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + new String[]{NoteColumns.SNIPPET}, + NoteColumns.ID + "=?", + new String[]{String.valueOf(noteId)}, + null); + + if (cursor != null) { + String snippet = ""; + if (cursor.moveToFirst()) { + // 获取摘要信息并返回 + snippet = cursor.getString(0); + } + cursor.close(); + return snippet; + } + // 如果未找到匹配的通话笔记,则抛出异常 + throw new IllegalArgumentException("Note is not found with id: " + noteId); + } + + public static String getFormattedSnippet(String snippet) { + // 格式化摘要信息,去除首尾空格并截取第一行 + if (snippet != null) { + snippet = snippet.trim(); + int index = snippet.indexOf('\n'); + if (index != -1) { + snippet = snippet.substring(0, index); + } + } + return snippet; + } + + From a0e3a9381b33dbfdebc2c882c4d83c4da95bdfd9 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:28:36 +0800 Subject: [PATCH 21/40] ADD file via upload --- src/tool/GTaskStringUtils.java | 113 +++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/tool/GTaskStringUtils.java diff --git a/src/tool/GTaskStringUtils.java b/src/tool/GTaskStringUtils.java new file mode 100644 index 0000000..666b729 --- /dev/null +++ b/src/tool/GTaskStringUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +public class GTaskStringUtils { + + public final static String GTASK_JSON_ACTION_ID = "action_id"; + + public final static String GTASK_JSON_ACTION_LIST = "action_list"; + + public final static String GTASK_JSON_ACTION_TYPE = "action_type"; + + public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; + + public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; + + public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; + + public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; + + public final static String GTASK_JSON_CREATOR_ID = "creator_id"; + + public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; + + public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; + + public final static String GTASK_JSON_COMPLETED = "completed"; + + public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; + + public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; + + public final static String GTASK_JSON_DELETED = "deleted"; + + public final static String GTASK_JSON_DEST_LIST = "dest_list"; + + public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; + + public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; + + public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; + + public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; + + public final static String GTASK_JSON_GET_DELETED = "get_deleted"; + + public final static String GTASK_JSON_ID = "id"; + + public final static String GTASK_JSON_INDEX = "index"; + + public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; + + public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; + + public final static String GTASK_JSON_LIST_ID = "list_id"; + + public final static String GTASK_JSON_LISTS = "lists"; + + public final static String GTASK_JSON_NAME = "name"; + + public final static String GTASK_JSON_NEW_ID = "new_id"; + + public final static String GTASK_JSON_NOTES = "notes"; + + public final static String GTASK_JSON_PARENT_ID = "parent_id"; + + public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; + + public final static String GTASK_JSON_RESULTS = "results"; + + public final static String GTASK_JSON_SOURCE_LIST = "source_list"; + + public final static String GTASK_JSON_TASKS = "tasks"; + + public final static String GTASK_JSON_TYPE = "type"; + + public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; + + public final static String GTASK_JSON_TYPE_TASK = "TASK"; + + public final static String GTASK_JSON_USER = "user"; + + public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; + + public final static String FOLDER_DEFAULT = "Default"; + + public final static String FOLDER_CALL_NOTE = "Call_Note"; + + public final static String FOLDER_META = "METADATA"; + + public final static String META_HEAD_GTASK_ID = "meta_gid"; + + public final static String META_HEAD_NOTE = "meta_note"; + + public final static String META_HEAD_DATA = "meta_data"; + + public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; + +} From f9ec56406ce743bc39731026ae1abe6f48384cca Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:28:45 +0800 Subject: [PATCH 22/40] ADD file via upload --- src/tool/ResourceParser.java | 294 +++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 src/tool/ResourceParser.java diff --git a/src/tool/ResourceParser.java b/src/tool/ResourceParser.java new file mode 100644 index 0000000..8309564 --- /dev/null +++ b/src/tool/ResourceParser.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +public class GTaskStringUtils { + + public final static String GTASK_JSON_ACTION_ID = "action_id"; + + public final static String GTASK_JSON_ACTION_LIST = "action_list"; + + public final static String GTASK_JSON_ACTION_TYPE = "action_type"; + + public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; + + public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; + + public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; + + public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; + + public final static String GTASK_JSON_CREATOR_ID = "creator_id"; + + public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; + + public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; + + public final static String GTASK_JSON_COMPLETED = "completed"; + + public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; + + public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; + + public final static String GTASK_JSON_DELETED = "deleted"; + + public final static String GTASK_JSON_DEST_LIST = "dest_list"; + + public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; + + public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; + + public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; + + public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; + + public final static String GTASK_JSON_GET_DELETED = "get_deleted"; + + public final static String GTASK_JSON_ID = "id"; + + public final static String GTASK_JSON_INDEX = "index"; + + public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; + + public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; + + public final static String GTASK_JSON_LIST_ID = "list_id"; + + public final static String GTASK_JSON_LISTS = "lists"; + + public final static String GTASK_JSON_NAME = "name"; + + public final static String GTASK_JSON_NEW_ID = "new_id"; + + public final static String GTASK_JSON_NOTES = "notes"; + + public final static String GTASK_JSON_PARENT_ID = "parent_id"; + + public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; + + public final static String GTASK_JSON_RESULTS = "results"; + + public final static String GTASK_JSON_SOURCE_LIST = "source_list"; + + public final static String GTASK_JSON_TASKS = "tasks"; + + public final static String GTASK_JSON_TYPE = "type"; + + public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; + + public final static String GTASK_JSON_TYPE_TASK = "TASK"; + + public final static String GTASK_JSON_USER = "user"; + + public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; + + public final static String FOLDER_DEFAULT = "Default"; + + public final static String FOLDER_CALL_NOTE = "Call_Note"; + + public final static String FOLDER_META = "METADATA"; + + public final static String META_HEAD_GTASK_ID = "meta_gid"; + + public final static String META_HEAD_NOTE = "meta_note"; + + public final static String META_HEAD_DATA = "meta_data"; + + public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; + +} +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.preference.PreferenceManager; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesPreferenceActivity; + +public class ResourceParser { + + public static final int YELLOW = 0; + public static final int BLUE = 1; + public static final int WHITE = 2; + public static final int GREEN = 3; + public static final int RED = 4; + + public static final int BG_DEFAULT_COLOR = YELLOW; + + public static final int TEXT_SMALL = 0; + public static final int TEXT_MEDIUM = 1; + public static final int TEXT_LARGE = 2; + public static final int TEXT_SUPER = 3; + + public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + + public static class NoteBgResources { + private final static int [] BG_EDIT_RESOURCES = new int [] { + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red + }; + + private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red + }; + + public static int getNoteBgResource(int id) { + return BG_EDIT_RESOURCES[id]; + } + + public static int getNoteTitleBgResource(int id) { + return BG_EDIT_TITLE_RESOURCES[id]; + } + } + + public static int getDefaultBgId(Context context) { + if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { + return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); + } else { + return BG_DEFAULT_COLOR; + } + } + + public static class NoteItemBgResources { + private final static int [] BG_FIRST_RESOURCES = new int [] { + R.drawable.list_yellow_up, + R.drawable.list_blue_up, + R.drawable.list_white_up, + R.drawable.list_green_up, + R.drawable.list_red_up + }; + + private final static int [] BG_NORMAL_RESOURCES = new int [] { + R.drawable.list_yellow_middle, + R.drawable.list_blue_middle, + R.drawable.list_white_middle, + R.drawable.list_green_middle, + R.drawable.list_red_middle + }; + + private final static int [] BG_LAST_RESOURCES = new int [] { + R.drawable.list_yellow_down, + R.drawable.list_blue_down, + R.drawable.list_white_down, + R.drawable.list_green_down, + R.drawable.list_red_down, + }; + + private final static int [] BG_SINGLE_RESOURCES = new int [] { + R.drawable.list_yellow_single, + R.drawable.list_blue_single, + R.drawable.list_white_single, + R.drawable.list_green_single, + R.drawable.list_red_single + }; + + public static int getNoteBgFirstRes(int id) { + return BG_FIRST_RESOURCES[id]; + } + + public static int getNoteBgLastRes(int id) { + return BG_LAST_RESOURCES[id]; + } + + public static int getNoteBgSingleRes(int id) { + return BG_SINGLE_RESOURCES[id]; + } + + public static int getNoteBgNormalRes(int id) { + return BG_NORMAL_RESOURCES[id]; + } + + public static int getFolderBgRes() { + return R.drawable.list_folder; + } + } + + public static class WidgetBgResources { + private final static int [] BG_2X_RESOURCES = new int [] { + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, + }; + + public static int getWidget2xBgResource(int id) { + return BG_2X_RESOURCES[id]; + } + + private final static int [] BG_4X_RESOURCES = new int [] { + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red + }; + + public static int getWidget4xBgResource(int id) { + return BG_4X_RESOURCES[id]; + } + } + + public static class TextAppearanceResources { + private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { + R.style.TextAppearanceNormal, + R.style.TextAppearanceMedium, + R.style.TextAppearanceLarge, + R.style.TextAppearanceSuper + }; + + public static int getTexAppearanceResource(int id) { + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if (id >= TEXTAPPEARANCE_RESOURCES.length) { + return BG_DEFAULT_FONT_SIZE; + } + return TEXTAPPEARANCE_RESOURCES[id]; + } + + public static int getResourcesSize() { + return TEXTAPPEARANCE_RESOURCES.length; + } + } +} From 6f7293455665615725ae7470e6e60e9f2f230676 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:29:07 +0800 Subject: [PATCH 23/40] ADD file via upload --- src/ui/AlarmAlertActivity.java | 189 +++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/ui/AlarmAlertActivity.java diff --git a/src/ui/AlarmAlertActivity.java b/src/ui/AlarmAlertActivity.java new file mode 100644 index 0000000..cc6349e --- /dev/null +++ b/src/ui/AlarmAlertActivity.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings; +import android.view.Window; +import android.view.WindowManager; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; + +import java.io.IOException; + + +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + private long mNoteId; // 闹钟提醒的便签ID + private String mSnippet; // 便签的摘要信息 + private static final int SNIPPET_PREW_MAX_LEN = 60; // 便签摘要的最大长度 + MediaPlayer mPlayer; // 播放器对象 + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); // 不显示标题栏 + + final Window win = getWindow(); + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 在锁屏界面上显示 + + // 如果屏幕未点亮,则保持屏幕点亮,并在锁屏时允许解锁 + if (!isScreenOn()) { + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + } + + Intent intent = getIntent(); + + try { + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 从Intent中获取便签ID + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 根据便签ID获取便签摘要 + // 如果摘要长度超过限制,截取部分内容并添加提示信息 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return; + } + + mPlayer = new MediaPlayer(); + // 如果便签在数据库中可见,则显示操作对话框并播放闹钟声音 + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + showActionDialog(); + playAlarmSound(); + } else { + finish(); // 如果便签不可见,结束当前Activity + } + } + + // 检查屏幕是否点亮 + private boolean isScreenOn() { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } +} + + + private void playAlarmSound() { + // 获取默认的警告铃声URI + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + + // 获取静音模式下受影响的音频流 + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + // 检查是否静音模式影响了警告音频流 + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { + // 设置音频流类型为受静音模式影响的流类型 + mPlayer.setAudioStreamType(silentModeStreams); + } else { + // 设置音频流类型为警告音频流类型 + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } + + try { + // 设置音频源为警告铃声URI + mPlayer.setDataSource(this, url); + // 准备音频播放器 + mPlayer.prepare(); + // 设置循环播放 + mPlayer.setLooping(true); + // 开始播放音频 + mPlayer.start(); + } catch (IllegalArgumentException e) { + // 参数异常,打印堆栈轨迹 + e.printStackTrace(); + } catch (SecurityException e) { + // 安全异常,打印堆栈轨迹 + e.printStackTrace(); + } catch (IllegalStateException e) { + // 状态异常,打印堆栈轨迹 + e.printStackTrace(); + } catch (IOException e) { + // I/O异常,打印堆栈轨迹 + e.printStackTrace(); + } + } + + private void showActionDialog() { + // 创建警告对话框 + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + // 设置对话框标题 + dialog.setTitle(R.string.app_name); + // 设置对话框消息内容 + dialog.setMessage(mSnippet); + // 设置对话框积极按钮及点击事件处理者 + dialog.setPositiveButton(R.string.notealert_ok, this); + + // 检查屏幕是否打开,如果是则设置对话框消极按钮及点击事件处理者 + if (isScreenOn()) { + dialog.setNegativeButton(R.string.notealert_enter, this); + } + + // 显示对话框,并设置对话框关闭监听事件处理者 + dialog.show().setOnDismissListener(this); + } + + + public void onClick(DialogInterface dialog, int which) { + // 按钮点击事件处理 + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + // 如果是消极按钮 + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); + // 启动编辑笔记的活动 + startActivity(intent); + break; + default: + break; + } + } + + public void onDismiss(DialogInterface dialog) { + // 对话框关闭事件处理 + stopAlarmSound(); // 停止警报音 + finish(); // 结束当前活动 + } + + // 停止警报音 + private void stopAlarmSound() { + if (mPlayer != null) { + mPlayer.stop(); // 停止音频播放 + mPlayer.release(); // 释放资源 + mPlayer = null; // 将播放器置空 + } + } + +} From ed794ca4c12f2a57d41ff6f5da306e5419ebd8af Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:29:20 +0800 Subject: [PATCH 24/40] ADD file via upload --- src/ui/AlarmInitReceiver.java | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/ui/AlarmInitReceiver.java diff --git a/src/ui/AlarmInitReceiver.java b/src/ui/AlarmInitReceiver.java new file mode 100644 index 0000000..0f31cfe --- /dev/null +++ b/src/ui/AlarmInitReceiver.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class AlarmInitReceiver extends BroadcastReceiver { + + private static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + @Override + public void onReceive(Context context, Intent intent) { + // 获取当前时间 + long currentDate = System.currentTimeMillis(); + + // 查询满足条件的提醒时间 + Cursor c = context.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String[]{String.valueOf(currentDate)}, + null + ); + + if (c != null) { + if (c.moveToFirst()) { + do { + // 获取提醒时间 + long alertDate = c.getLong(COLUMN_ALERTED_DATE); + + // 创建用于触发闹钟的Intent + Intent sender = new Intent(context, AlarmReceiver.class); + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + + // 创建PendingIntent + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + + // 获取AlarmManager实例 + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + // 设置闹钟 + alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); + } + c.close(); + } + } +} From 66ec2a52fc654007d534419989b76b064dd38409 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:29:31 +0800 Subject: [PATCH 25/40] ADD file via upload --- src/ui/AlarmReceiver.java | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/ui/AlarmReceiver.java diff --git a/src/ui/AlarmReceiver.java b/src/ui/AlarmReceiver.java new file mode 100644 index 0000000..fe2b18e --- /dev/null +++ b/src/ui/AlarmReceiver.java @@ -0,0 +1,36 @@ +/* + * 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) { + // 设置要启动的Activity类为AlarmAlertActivity + intent.setClass(context, AlarmAlertActivity.class); + + // 给Intent添加标志,表示要在新的任务中启动Activity + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // 启动Activity + context.startActivity(intent); + } +} + From eec327871bf9eb97f15beb787c18e5521471522b Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:29:36 +0800 Subject: [PATCH 26/40] ADD file via upload --- src/ui/DateTimePicker.java | 485 +++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 src/ui/DateTimePicker.java diff --git a/src/ui/DateTimePicker.java b/src/ui/DateTimePicker.java new file mode 100644 index 0000000..496b0cd --- /dev/null +++ b/src/ui/DateTimePicker.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +import net.micode.notes.R; + + +import android.content.Context; +import android.text.format.DateFormat; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.NumberPicker; + +public class DateTimePicker extends FrameLayout { + + private static final boolean DEFAULT_ENABLE_STATE = true; + + private static final int HOURS_IN_HALF_DAY = 12; + private static final int HOURS_IN_ALL_DAY = 24; + private static final int DAYS_IN_ALL_WEEK = 7; + private static final int DATE_SPINNER_MIN_VAL = 0; + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + private static final int MINUT_SPINNER_MIN_VAL = 0; + private static final int MINUT_SPINNER_MAX_VAL = 59; + private static final int AMPM_SPINNER_MIN_VAL = 0; + private static final int AMPM_SPINNER_MAX_VAL = 1; + + private final NumberPicker mDateSpinner; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + private Calendar mDate; + + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + private boolean mIsAm; + + private boolean mIs24HourView; + + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + private boolean mInitialising; + + private OnDateTimeChangedListener mOnDateTimeChangedListener; + + private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + updateDateControl(); + onDateTimeChanged(); + } + }; + + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + if (!mIs24HourView) { + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + } + if (offset != 0) { + mDate.add(Calendar.HOUR_OF_DAY, offset); + mHourSpinner.setValue(getCurrentHour()); + updateDateControl(); + int newHour = getCurrentHourOfDay(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + mDate.set(Calendar.MINUTE, newVal); + onDateTimeChanged(); + } + }; + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mIsAm = !mIsAm; + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + onDateTimeChanged(); + } + }; + + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + } + + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); + } + + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); + mAmPmSpinner.setDisplayedValues(stringsForAmPm); + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); + + // update controls to initial state + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView); + + // set to current time + setCurrentDate(date); + + setEnabled(isEnabled()); + + // set the content descriptions + mInitialising = false; + } + + @Override + public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; + } + super.setEnabled(enabled); + mDateSpinner.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * Get the current date in millis + * + * @return the current date in millis + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + } + + /** + * Set the current date + * + * @param date The current date in millis + */ + public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); + } + + /** + * Set the current date + * + * @param year The current year + * @param month The current month + * @param dayOfMonth The current dayOfMonth + * @param hourOfDay The current hourOfDay + * @param minute The current minute + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); + } + + /** + * Get current year + * + * @return The current year + */ + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + + /** + * Set current year + * + * @param year The current year + */ + public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current month in the year + * + * @return The current month in the year + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + /** + * Set current month in the year + * + * @param month The month in the year + */ + public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current day of the month + * + * @return The day of the month + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * Set current day of the month + * + * @param dayOfMonth The day of the month + */ + public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current hour in 24 hour mode, in the range (0~23) + * @return The current hour in 24 hour mode + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + /** + * Set current hour in 24 hour mode, in the range (0~23) + * + * @param hourOfDay + */ + public void setCurrentHour(int hourOfDay) { + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); + } + + /** + * Get currentMinute + * + * @return The Current Minute + */ + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + } + + /** + * Set current minute + */ + public void setCurrentMinute(int minute) { + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); + } + + /** + * @return true if this is in 24 hour view else false. + */ + public boolean is24HourView () { + return mIs24HourView; + } + + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True for 24 hour mode. False for AM/PM mode. + */ + public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); + } + + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); + mDateSpinner.setDisplayedValues(null); + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate(); + } + + private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } + } + + private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } + } + + /** + * Set the callback that indicates the 'Set' button has been pressed. + * @param callback the callback, if null will do nothing + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { + mOnDateTimeChangedListener = callback; + } + + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} From d9ece2ef5564ffe46593af60c9e4d9efab8812a3 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:29:43 +0800 Subject: [PATCH 27/40] ADD file via upload --- src/ui/DateTimePickerDialog.java | 123 +++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/ui/DateTimePickerDialog.java diff --git a/src/ui/DateTimePickerDialog.java b/src/ui/DateTimePickerDialog.java new file mode 100644 index 0000000..b2a4c46 --- /dev/null +++ b/src/ui/DateTimePickerDialog.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import java.util.Calendar; + +import net.micode.notes.R; +import net.micode.notes.ui.DateTimePicker; +import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.text.format.DateFormat; +import android.text.format.DateUtils; + +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + private Calendar mDate = Calendar.getInstance(); // 日历实例,用于存储日期和时间信息 + private boolean mIs24HourView; // 用于标识是否为24小时制 + private OnDateTimeSetListener mOnDateTimeSetListener; // 回调接口,用于监听日期时间设置事件 + private DateTimePicker mDateTimePicker; // 自定义的日期时间选择器 + + // 回调接口,用于监听日期时间设置事件 + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); + } + + // 构造方法,接收上下文和初始日期时间值 + public DateTimePickerDialog(Context context, long date) { + super(context); + + // 初始化日期时间选择器 + mDateTimePicker = new DateTimePicker(context); + setView(mDateTimePicker); + + // 设置日期时间变化监听器,以更新mDate对象和对话框标题 + mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { + public void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + mDate.set(Calendar.YEAR, year); + mDate.set(Calendar.MONTH, month); + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + mDate.set(Calendar.MINUTE, minute); + updateTitle(mDate.getTimeInMillis()); + } + }); + + // 初始化mDate对象,将秒数置零 + mDate.setTimeInMillis(date); + mDate.set(Calendar.SECOND, 0); + + // 设置日期时间选择器的初始日期 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + + // 设置"确定"按钮,并指定点击监听器为当前实例 + setButton(context.getString(R.string.datetime_dialog_ok), this); + + // 设置"取消"按钮,点击监听器为null + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + + // 根据系统设置,确定是否为24小时制 + set24HourView(DateFormat.is24HourFormat(this.getContext())); + + // 更新对话框标题 + updateTitle(mDate.getTimeInMillis()); + } + + // 更新对话框标题 + private void updateTitle(long timeInMillis) { + // 可根据需求更新对话框标题的显示方式 + // 这里可能涉及日期时间格式化等操作 + // 未提供相关代码,需要根据实际情况自行添加 + } + + + // 设置是否为24小时制 + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + } + + // 设置日期时间设置监听器 + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { + mOnDateTimeSetListener = callBack; + } + + // 更新对话框标题 + private void updateTitle(long date) { + // 根据标志位设置日期时间的显示格式 + int flag = + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; + flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : 0; // 根据是否为24小时制设置标志位 + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); // 使用DateUtils格式化日期时间并设置为对话框标题 + } + + // 确定按钮点击事件处理 + public void onClick(DialogInterface arg0, int arg1) { + // 检查是否设置了日期时间设置监听器,如果设置了,则调用回调方法 + if (mOnDateTimeSetListener != null) { + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + } + + +} \ No newline at end of file From 69255894fbc10a1e245516688fa444cfa0ff7ee4 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:29:49 +0800 Subject: [PATCH 28/40] ADD file via upload --- src/ui/DropdownMenu.java | 85 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/ui/DropdownMenu.java diff --git a/src/ui/DropdownMenu.java b/src/ui/DropdownMenu.java new file mode 100644 index 0000000..d5feeb3 --- /dev/null +++ b/src/ui/DropdownMenu.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; + +import net.micode.notes.R; + +public class DropdownMenu { + private Button mButton; // 下拉菜单的触发按钮 + private PopupMenu mPopupMenu; // 弹出式菜单 + private Menu mMenu; // 菜单项集合 + + /** + * 构造函数,用于初始化DropdownMenu + * + * @param context 上下文对象 + * @param button 与下拉菜单关联的按钮 + * @param menuId 菜单资源文件的ID + */ + public DropdownMenu(Context context, Button button, int menuId) { + mButton = button; + mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景为下拉图标 + mPopupMenu = new PopupMenu(context, mButton); // 创建弹出式菜单,并与按钮关联 + mMenu = mPopupMenu.getMenu(); // 获取菜单项集合 + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 从资源文件中加载菜单项到菜单集合 + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mPopupMenu.show(); // 点击按钮时显示弹出式菜单 + } + }); + } + + /** + * 设置下拉菜单项点击监听器 + * + * @param listener 下拉菜单项点击监听器 + */ + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu != null) { + mPopupMenu.setOnMenuItemClickListener(listener); // 设置下拉菜单项点击监听器 + } + } + + /** + * 根据菜单项ID查找菜单项 + * + * @param id 要查找的菜单项的ID + * @return 查找到的菜单项,如果不存在则为null + */ + public MenuItem findItem(int id) { + return mMenu.findItem(id); // 根据ID在菜单项集合中查找菜单项 + } + + /** + * 设置下拉菜单按钮的标题 + * + * @param title 要设置的标题文本 + */ + public void setTitle(CharSequence title) { + mButton.setText(title); // 设置下拉菜单按钮的标题文本 + } +} + From c9c75326b374fecd53c48f868760ca783946da7e Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:30:01 +0800 Subject: [PATCH 29/40] ADD file via upload --- src/ui/FoldersListAdapter.java | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/ui/FoldersListAdapter.java diff --git a/src/ui/FoldersListAdapter.java b/src/ui/FoldersListAdapter.java new file mode 100644 index 0000000..f09d1df --- /dev/null +++ b/src/ui/FoldersListAdapter.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class FoldersListAdapter extends CursorAdapter { + public static final String [] PROJECTION = { + NoteColumns.ID, // 笔记ID列 + NoteColumns.SNIPPET // 笔记摘要列 + }; + + public static final int ID_COLUMN = 0; // ID列的索引 + public static final int NAME_COLUMN = 1; // 名称列的索引 + + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); + // TODO Auto-generated constructor stub + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new FolderListItem(context); // 创建新的文件夹列表项视图 + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof FolderListItem) { + // 获取文件夹名称或显示移动到父文件夹的选项 + String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + ((FolderListItem) view).bind(folderName); // 将文件夹名称绑定到列表项视图 + } + } + + /** + * 获取指定位置的文件夹名称 + * + * @param context 上下文对象 + * @param position 位置索引 + * @return 文件夹名称或移动到父文件夹的选项 + */ + public String getFolderName(Context context, int position) { + Cursor cursor = (Cursor) getItem(position); + return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + } + + private class FolderListItem extends LinearLayout { + private TextView mName; // 文件夹名称文本视图 + + public FolderListItem(Context context) { + super(context); + inflate(context, R.layout.folder_list_item, this); // 填充文件夹列表项视图 + mName = (TextView) findViewById(R.id.tv_folder_name); // 获取文件夹名称文本视图的引用 + } + + /** + * 将文件夹名称绑定到文件夹列表项视图 + * + * @param name 文件夹名称或移动到父文件夹的选项 + */ + public void bind(String name) { + mName.setText(name); // 设置文件夹名称文本 + } + } +} From 2c8d4a290053d0c1f9cc4b50ed0924d9c9eb1f8e Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:30:11 +0800 Subject: [PATCH 30/40] ADD file via upload --- src/ui/NoteEditActivity.java | 873 +++++++++++++++++++++++++++++++++++ 1 file changed, 873 insertions(+) create mode 100644 src/ui/NoteEditActivity.java diff --git a/src/ui/NoteEditActivity.java b/src/ui/NoteEditActivity.java new file mode 100644 index 0000000..96a9ff8 --- /dev/null +++ b/src/ui/NoteEditActivity.java @@ -0,0 +1,873 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.app.SearchManager; +import android.appwidget.AppWidgetManager; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Paint; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.style.BackgroundColorSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +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.TextNote; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.tool.ResourceParser.TextAppearanceResources; +import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; +import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class NoteEditActivity extends Activity implements OnClickListener, + NoteSettingChangedListener, OnTextViewChangeListener { + private class HeadViewHolder { + public TextView tvModified; + + public ImageView ivAlertIcon; + + public TextView tvAlertDate; + + public ImageView ibSetBgColor; + } + + private static final Map sBgSelectorBtnsMap = new HashMap(); + static { + sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); + sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); + sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); + sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); + sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); + } + + private static final Map sBgSelectorSelectionMap = new HashMap(); + static { + sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); + sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); + sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); + sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); + sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); + } + + private static final Map sFontSizeBtnsMap = new HashMap(); + static { + sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); + sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); + sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); + sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); + } + + private static final Map sFontSelectorSelectionMap = new HashMap(); + static { + sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); + } + + private static final String TAG = "NoteEditActivity"; + + private HeadViewHolder mNoteHeaderHolder; + + private View mHeadViewPanel; + + private View mNoteBgColorSelector; + + private View mFontSizeSelector; + + private EditText mNoteEditor; + + private View mNoteEditorPanel; + + private WorkingNote mWorkingNote; + + private SharedPreferences mSharedPrefs; + private int mFontSizeId; + + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + + public static final String TAG_CHECKED = String.valueOf('\u221A'); + public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + + private LinearLayout mEditTextList; + + private String mUserQuery; + private Pattern mPattern; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.note_edit); + + if (savedInstanceState == null && !initActivityState(getIntent())) { + finish(); + return; + } + initResources(); + } + + /** + * Current activity may be killed when the memory is low. Once it is killed, for another time + * user load this activity, we should restore the former state + */ + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + if (!initActivityState(intent)) { + finish(); + return; + } + Log.d(TAG, "Restoring from killed activity"); + } + } + + private boolean initActivityState(Intent intent) { + /** + * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, + * then jump to the NotesListActivity + */ + mWorkingNote = null; + if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); + mUserQuery = ""; + + /** + * Starting from the searched result + */ + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { + noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); + mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); + } + + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { + Intent jump = new Intent(this, NotesListActivity.class); + startActivity(jump); + showToast(R.string.error_note_not_exist); + finish(); + return false; + } else { + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load note failed with note id" + noteId); + finish(); + return false; + } + } + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // New note + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, + Notes.TYPE_WIDGET_INVALIDE); + int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this)); + + // Parse call-record note + String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); + long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); + if (callDate != 0 && phoneNumber != null) { + if (TextUtils.isEmpty(phoneNumber)) { + Log.w(TAG, "The call record number is null"); + } + long noteId = 0; + if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), + phoneNumber, callDate)) > 0) { + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load call note failed with note id" + noteId); + finish(); + return false; + } + } else { + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, + widgetType, bgResId); + mWorkingNote.convertToCallNote(phoneNumber, callDate); + } + } else { + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, + bgResId); + } + + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } else { + Log.e(TAG, "Intent not specified action, should not support"); + finish(); + return false; + } + mWorkingNote.setOnSettingStatusChangedListener(this); + return true; + } + + @Override + protected void onResume() { + super.onResume(); + initNoteScreen(); + } + + private void initNoteScreen() { + mNoteEditor.setTextAppearance(this, TextAppearanceResources + .getTexAppearanceResource(mFontSizeId)); + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + switchToListMode(mWorkingNote.getContent()); + } else { + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + mNoteEditor.setSelection(mNoteEditor.getText().length()); + } + for (Integer id : sBgSelectorSelectionMap.keySet()) { + findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); + } + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + + mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, + mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR)); + + /** + * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker + * is not ready + */ + showAlertHeader(); + } + + private void showAlertHeader() { + if (mWorkingNote.hasClockAlert()) { + long time = System.currentTimeMillis(); + if (time > mWorkingNote.getAlertDate()) { + mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); + } else { + mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( + mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); + } + mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); + } else { + mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); + }; + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + initActivityState(intent); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + /** + * For new note without note id, we should firstly save it to + * generate a id. If the editing note is not worth saving, there + * is no id which is equivalent to create new note + */ + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mNoteBgColorSelector, ev)) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } + + if (mFontSizeSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mFontSizeSelector, ev)) { + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return super.dispatchTouchEvent(ev); + } + + private boolean inRangeOfView(View view, MotionEvent ev) { + int []location = new int[2]; + view.getLocationOnScreen(location); + int x = location[0]; + int y = location[1]; + if (ev.getX() < x + || ev.getX() > (x + view.getWidth()) + || ev.getY() < y + || ev.getY() > (y + view.getHeight())) { + return false; + } + return true; + } + + private void initResources() { + mHeadViewPanel = findViewById(R.id.note_title); + mNoteHeaderHolder = new HeadViewHolder(); + mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); + mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); + mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); + mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); + mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + mNoteEditor = (EditText) findViewById(R.id.note_edit_view); + mNoteEditorPanel = findViewById(R.id.sv_note_edit); + mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); + for (int id : sBgSelectorBtnsMap.keySet()) { + ImageView iv = (ImageView) findViewById(id); + iv.setOnClickListener(this); + } + + mFontSizeSelector = findViewById(R.id.font_size_selector); + for (int id : sFontSizeBtnsMap.keySet()) { + View view = findViewById(id); + view.setOnClickListener(this); + }; + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { + mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; + } + mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); + } + + @Override + protected void onPause() { + super.onPause(); + if(saveNote()) { + Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); + } + clearSettingState(); + } + + private void updateWidget() { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); + } else { + Log.e(TAG, "Unspported widget type"); + return; + } + + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { + mWorkingNote.getWidgetId() + }); + + sendBroadcast(intent); + setResult(RESULT_OK, intent); + } + + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.btn_set_bg_color) { + mNoteBgColorSelector.setVisibility(View.VISIBLE); + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + - View.VISIBLE); + } else if (sBgSelectorBtnsMap.containsKey(id)) { + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.GONE); + mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); + mNoteBgColorSelector.setVisibility(View.GONE); + } else if (sFontSizeBtnsMap.containsKey(id)) { + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); + mFontSizeId = sFontSizeBtnsMap.get(id); + mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + getWorkingText(); + switchToListMode(mWorkingNote.getContent()); + } else { + mNoteEditor.setTextAppearance(this, + TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + } + mFontSizeSelector.setVisibility(View.GONE); + } + } + + @Override + public void onBackPressed() { + if(clearSettingState()) { + return; + } + + saveNote(); + super.onBackPressed(); + } + + private boolean clearSettingState() { + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return false; + } + + public void onBackgroundColorChanged() { + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.VISIBLE); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (isFinishing()) { + return true; + } + clearSettingState(); + menu.clear(); + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_note_edit, menu); + } else { + getMenuInflater().inflate(R.menu.note_edit, menu); + } + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); + } else { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); + } + if (mWorkingNote.hasClockAlert()) { + menu.findItem(R.id.menu_alert).setVisible(false); + } else { + menu.findItem(R.id.menu_delete_remind).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_note: + createNewNote(); + break; + case R.id.menu_delete: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.alert_title_delete)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getString(R.string.alert_message_delete_note)); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + deleteCurrentNote(); + finish(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case R.id.menu_font_size: + mFontSizeSelector.setVisibility(View.VISIBLE); + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + break; + case R.id.menu_list_mode: + mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? + TextNote.MODE_CHECK_LIST : 0); + break; + case R.id.menu_share: + getWorkingText(); + sendTo(this, mWorkingNote.getContent()); + break; + case R.id.menu_send_to_desktop: + sendToDesktop(); + break; + case R.id.menu_alert: + setReminder(); + break; + case R.id.menu_delete_remind: + mWorkingNote.setAlertDate(0, false); + break; + default: + break; + } + return true; + } + + private void setReminder() { + DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); + d.setOnDateTimeSetListener(new OnDateTimeSetListener() { + public void OnDateTimeSet(AlertDialog dialog, long date) { + mWorkingNote.setAlertDate(date , true); + } + }); + d.show(); + } + + /** + * Share note to apps that support {@link Intent#ACTION_SEND} action + * and {@text/plain} type + */ + private void sendTo(Context context, String info) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, info); + intent.setType("text/plain"); + context.startActivity(intent); + } + + private void createNewNote() { + // Firstly, save current editing notes + saveNote(); + + // For safety, start a new NoteEditActivity + finish(); + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + startActivity(intent); + } + + private void deleteCurrentNote() { + if (mWorkingNote.existInDatabase()) { + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId(); + if (id != Notes.ID_ROOT_FOLDER) { + ids.add(id); + } else { + Log.d(TAG, "Wrong note id, should not happen"); + } + if (!isSyncMode()) { + if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { + Log.e(TAG, "Delete Note error"); + } + } else { + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + } + mWorkingNote.markDeleted(true); + } + + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + + public void onClockAlertChanged(long date, boolean set) { + /** + * User could set clock to an unsaved note, so before setting the + * alert clock, we should save the note first + */ + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + if (mWorkingNote.getNoteId() > 0) { + Intent intent = new Intent(this, AlarmReceiver.class); + intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); + AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); + showAlertHeader(); + if(!set) { + alarmManager.cancel(pendingIntent); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); + } + } else { + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Clock alert setting error"); + showToast(R.string.error_note_empty_for_clock); + } + } + + public void onWidgetChanged() { + updateWidget(); + } + + public void onEditTextDelete(int index, String text) { + int childCount = mEditTextList.getChildCount(); + if (childCount == 1) { + return; + } + + for (int i = index + 1; i < childCount; i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i - 1); + } + + mEditTextList.removeViewAt(index); + NoteEditText edit = null; + if(index == 0) { + edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( + R.id.et_edit_text); + } else { + edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( + R.id.et_edit_text); + } + int length = edit.length(); + edit.append(text); + edit.requestFocus(); + edit.setSelection(length); + } + + public void onEditTextEnter(int index, String text) { + /** + * Should not happen, check for debug + */ + if(index > mEditTextList.getChildCount()) { + Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); + } + + View view = getListItem(text, index); + mEditTextList.addView(view, index); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + edit.requestFocus(); + edit.setSelection(0); + for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i); + } + } + + private void switchToListMode(String text) { + mEditTextList.removeAllViews(); + String[] items = text.split("\n"); + int index = 0; + for (String item : items) { + if(!TextUtils.isEmpty(item)) { + mEditTextList.addView(getListItem(item, index)); + index++; + } + } + mEditTextList.addView(getListItem("", index)); + mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); + + mNoteEditor.setVisibility(View.GONE); + mEditTextList.setVisibility(View.VISIBLE); + } + + private Spannable getHighlightQueryResult(String fullText, String userQuery) { + SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); + if (!TextUtils.isEmpty(userQuery)) { + mPattern = Pattern.compile(userQuery); + Matcher m = mPattern.matcher(fullText); + int start = 0; + while (m.find(start)) { + spannable.setSpan( + new BackgroundColorSpan(this.getResources().getColor( + R.color.user_query_highlight)), m.start(), m.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start = m.end(); + } + } + return spannable; + } + + private View getListItem(String item, int index) { + View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); + final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); + cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + } + } + }); + + if (item.startsWith(TAG_CHECKED)) { + cb.setChecked(true); + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + item = item.substring(TAG_CHECKED.length(), item.length()).trim(); + } else if (item.startsWith(TAG_UNCHECKED)) { + cb.setChecked(false); + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); + } + + edit.setOnTextViewChangeListener(this); + edit.setIndex(index); + edit.setText(getHighlightQueryResult(item, mUserQuery)); + return view; + } + + public void onTextChange(int index, boolean hasText) { + if (index >= mEditTextList.getChildCount()) { + Log.e(TAG, "Wrong index, should not happen"); + return; + } + if(hasText) { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); + } else { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); + } + } + + public void onCheckListModeChanged(int oldMode, int newMode) { + if (newMode == TextNote.MODE_CHECK_LIST) { + switchToListMode(mNoteEditor.getText().toString()); + } else { + if (!getWorkingText()) { + mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", + "")); + } + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + mEditTextList.setVisibility(View.GONE); + mNoteEditor.setVisibility(View.VISIBLE); + } + } + + private boolean getWorkingText() { + boolean hasChecked = false; + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + View view = mEditTextList.getChildAt(i); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + if (!TextUtils.isEmpty(edit.getText())) { + if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { + sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); + hasChecked = true; + } else { + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); + } + } + } + mWorkingNote.setWorkingText(sb.toString()); + } else { + mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + } + return hasChecked; + } + + private boolean saveNote() { + getWorkingText(); + boolean saved = mWorkingNote.saveNote(); + if (saved) { + /** + * There are two modes from List view to edit view, open one note, + * create/edit a node. Opening node requires to the original + * position in the list when back from edit view, while creating a + * new node requires to the top of the list. This code + * {@link #RESULT_OK} is used to identify the create/edit state + */ + setResult(RESULT_OK); + } + return saved; + } + + private void sendToDesktop() { + /** + * Before send message to home, we should make sure that current + * editing note is exists in databases. So, for new note, firstly + * save it + */ + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + + if (mWorkingNote.getNoteId() > 0) { + Intent sender = new Intent(); + Intent shortcutIntent = new Intent(this, NoteEditActivity.class); + shortcutIntent.setAction(Intent.ACTION_VIEW); + shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + makeShortcutIconTitle(mWorkingNote.getContent())); + sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); + sender.putExtra("duplicate", true); + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + showToast(R.string.info_note_enter_desktop); + sendBroadcast(sender); + } else { + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Send to desktop error"); + showToast(R.string.error_note_empty_for_send_to_desktop); + } + } + + private String makeShortcutIconTitle(String content) { + content = content.replace(TAG_CHECKED, ""); + content = content.replace(TAG_UNCHECKED, ""); + return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, + SHORTCUT_ICON_TITLE_MAX_LEN) : content; + } + + private void showToast(int resId) { + showToast(resId, Toast.LENGTH_SHORT); + } + + private void showToast(int resId, int duration) { + Toast.makeText(this, resId, duration).show(); + } +} From e1a6b0e17f6bccc235a198de8d9f1de2737002c7 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:30:45 +0800 Subject: [PATCH 31/40] ADD file via upload --- src/ui/NoteEditText.java | 217 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/ui/NoteEditText.java diff --git a/src/ui/NoteEditText.java b/src/ui/NoteEditText.java new file mode 100644 index 0000000..2afe2a8 --- /dev/null +++ b/src/ui/NoteEditText.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.graphics.Rect; +import android.text.Layout; +import android.text.Selection; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.URLSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.widget.EditText; + +import net.micode.notes.R; + +import java.util.HashMap; +import java.util.Map; + +public class NoteEditText extends EditText { + private static final String TAG = "NoteEditText"; + private int mIndex; + private int mSelectionStartBeforeDelete; + + private static final String SCHEME_TEL = "tel:" ; + private static final String SCHEME_HTTP = "http:" ; + private static final String SCHEME_EMAIL = "mailto:" ; + + private static final Map sSchemaActionResMap = new HashMap(); + static { + sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); + } + + /** + * Call by the {@link NoteEditActivity} to delete or add edit text + */ + public interface OnTextViewChangeListener { + /** + * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens + * and the text is null + */ + void onEditTextDelete(int index, String text); + + /** + * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} + * happen + */ + void onEditTextEnter(int index, String text); + + /** + * Hide or show item option when text change + */ + void onTextChange(int index, boolean hasText); + } + + private OnTextViewChangeListener mOnTextViewChangeListener; + + public NoteEditText(Context context) { + super(context, null); + mIndex = 0; + } + + public void setIndex(int index) { + mIndex = index; + } + + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + + int x = (int) event.getX(); + int y = (int) event.getY(); + x -= getTotalPaddingLeft(); + y -= getTotalPaddingTop(); + x += getScrollX(); + y += getScrollY(); + + Layout layout = getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + Selection.setSelection(getText(), off); + break; + } + + return super.onTouchEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener != null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL: + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) { + case KeyEvent.KEYCODE_DEL: + if (mOnTextViewChangeListener != null) { + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + return true; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener != null) { + int selectionStart = getSelectionStart(); + String text = getText().subSequence(selectionStart, length()).toString(); + setText(getText().subSequence(0, selectionStart)); + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + default: + break; + } + return super.onKeyUp(keyCode, event); + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + if (mOnTextViewChangeListener != null) { + if (!focused && TextUtils.isEmpty(getText())) { + mOnTextViewChangeListener.onTextChange(mIndex, false); + } else { + mOnTextViewChangeListener.onTextChange(mIndex, true); + } + } + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + @Override + protected void onCreateContextMenu(ContextMenu menu) { + if (getText() instanceof Spanned) { + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + + int min = Math.min(selStart, selEnd); + int max = Math.max(selStart, selEnd); + + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); + if (urls.length == 1) { + int defaultResId = 0; + for(String schema: sSchemaActionResMap.keySet()) { + if(urls[0].getURL().indexOf(schema) >= 0) { + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) { + defaultResId = R.string.note_link_other; + } + + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // goto a new intent + urls[0].onClick(NoteEditText.this); + return true; + } + }); + } + } + super.onCreateContextMenu(menu); + } +} From 9cb391fb6862d744a88c3b9ad02de28fa052b294 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:30:54 +0800 Subject: [PATCH 32/40] ADD file via upload --- src/ui/NoteItemData.java | 224 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 src/ui/NoteItemData.java diff --git a/src/ui/NoteItemData.java b/src/ui/NoteItemData.java new file mode 100644 index 0000000..0f5a878 --- /dev/null +++ b/src/ui/NoteItemData.java @@ -0,0 +1,224 @@ +/* + * 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); + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + mParentId = cursor.getLong(PARENT_ID_COLUMN); + mSnippet = cursor.getString(SNIPPET_COLUMN); + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( + NoteEditActivity.TAG_UNCHECKED, ""); + mType = cursor.getInt(TYPE_COLUMN); + mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + + mPhoneNumber = ""; + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); + if (!TextUtils.isEmpty(mPhoneNumber)) { + mName = Contact.getContact(context, mPhoneNumber); + if (mName == null) { + mName = mPhoneNumber; + } + } + } + + if (mName == null) { + mName = ""; + } + checkPostion(cursor); + } + + private void checkPostion(Cursor cursor) { + mIsLastItem = cursor.isLast() ? true : false; + mIsFirstItem = cursor.isFirst() ? true : false; + mIsOnlyOneItem = (cursor.getCount() == 1); + mIsMultiNotesFollowingFolder = false; + mIsOneNoteFollowingFolder = false; + + if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { + int position = cursor.getPosition(); + if (cursor.moveToPrevious()) { + if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER + || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + if (cursor.getCount() > (position + 1)) { + mIsMultiNotesFollowingFolder = true; + } else { + mIsOneNoteFollowingFolder = true; + } + } + if (!cursor.moveToNext()) { + throw new IllegalStateException("cursor move to previous but can't move back"); + } + } + } + } + + public boolean isOneFollowingFolder() { + return mIsOneNoteFollowingFolder; + } + + public boolean isMultiFollowingFolder() { + return mIsMultiNotesFollowingFolder; + } + + public boolean isLast() { + return mIsLastItem; + } + + public String getCallName() { + return mName; + } + + public boolean isFirst() { + return mIsFirstItem; + } + + public boolean isSingle() { + return mIsOnlyOneItem; + } + + public long getId() { + return mId; + } + + public long getAlertDate() { + return mAlertDate; + } + + public long getCreatedDate() { + return mCreatedDate; + } + + public boolean hasAttachment() { + return mHasAttachment; + } + + public long getModifiedDate() { + return mModifiedDate; + } + + public int getBgColorId() { + return mBgColorId; + } + + public long getParentId() { + return mParentId; + } + + public int getNotesCount() { + return mNotesCount; + } + + public long getFolderId () { + return mParentId; + } + + public int getType() { + return mType; + } + + public int getWidgetType() { + return mWidgetType; + } + + public int getWidgetId() { + return mWidgetId; + } + + public String getSnippet() { + return mSnippet; + } + + public boolean hasAlert() { + return (mAlertDate > 0); + } + + public boolean isCallRecord() { + return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); + } + + public static int getNoteType(Cursor cursor) { + return cursor.getInt(TYPE_COLUMN); + } +} From fa6977941100fa889583626e7a993285d338bf64 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:31:02 +0800 Subject: [PATCH 33/40] ADD file via upload --- src/ui/NotesListActivity.java | 954 ++++++++++++++++++++++++++++++++++ 1 file changed, 954 insertions(+) create mode 100644 src/ui/NotesListActivity.java diff --git a/src/ui/NotesListActivity.java b/src/ui/NotesListActivity.java new file mode 100644 index 0000000..e843aec --- /dev/null +++ b/src/ui/NotesListActivity.java @@ -0,0 +1,954 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.appwidget.AppWidgetManager; +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Display; +import android.view.HapticFeedbackConstants; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.PopupMenu; +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; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.tool.BackupUtils; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; + +public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { + private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; + + private static final int FOLDER_LIST_QUERY_TOKEN = 1; + + private static final int MENU_FOLDER_DELETE = 0; + + private static final int MENU_FOLDER_VIEW = 1; + + private static final int MENU_FOLDER_CHANGE_NAME = 2; + + private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + + private enum ListEditState { + NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + }; + + private ListEditState mState; + + private BackgroundQueryHandler mBackgroundQueryHandler; + + private NotesListAdapter mNotesListAdapter; + + private ListView mNotesListView; + + private Button mAddNewNote; + + private boolean mDispatch; + + private int mOriginY; + + private int mDispatchY; + + private TextView mTitleBar; + + private long mCurrentFolderId; + + private ContentResolver mContentResolver; + + private ModeCallback mModeCallBack; + + private static final String TAG = "NotesListActivity"; + + public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; + + private NoteItemData mFocusNoteDataItem; + + private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; + + private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + + NoteColumns.NOTES_COUNT + ">0)"; + + private final static int REQUEST_CODE_OPEN_NODE = 102; + private final static int REQUEST_CODE_NEW_NODE = 103; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.note_list); + initResources(); + + /** + * Insert an introduction when user firstly use this application + */ + setAppInfoFromRawRes(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_OK + && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + mNotesListAdapter.changeCursor(null); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + private void setAppInfoFromRawRes() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { + StringBuilder sb = new StringBuilder(); + InputStream in = null; + try { + in = getResources().openRawResource(R.raw.introduction); + if (in != null) { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + char [] buf = new char[1024]; + int len = 0; + while ((len = br.read(buf)) > 0) { + sb.append(buf, 0, len); + } + } else { + Log.e(TAG, "Read introduction file error"); + return; + } + } catch (IOException e) { + e.printStackTrace(); + return; + } finally { + if(in != null) { + try { + in.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, + AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, + ResourceParser.RED); + note.setWorkingText(sb.toString()); + if (note.saveNote()) { + sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); + } else { + Log.e(TAG, "Save introduction note error"); + return; + } + } + } + + @Override + protected void onStart() { + super.onStart(); + startAsyncNotesListQuery(); + } + + private void initResources() { + mContentResolver = this.getContentResolver(); + mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mNotesListView = (ListView) findViewById(R.id.notes_list); + mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), + null, false); + mNotesListView.setOnItemClickListener(new OnListItemClickListener()); + mNotesListView.setOnItemLongClickListener(this); + mNotesListAdapter = new NotesListAdapter(this); + mNotesListView.setAdapter(mNotesListAdapter); + mAddNewNote = (Button) findViewById(R.id.btn_new_note); + mAddNewNote.setOnClickListener(this); + mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + mDispatch = false; + mDispatchY = 0; + mOriginY = 0; + mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + mState = ListEditState.NOTE_LIST; + mModeCallBack = new ModeCallback(); + } + + private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + private DropdownMenu mDropDownMenu; + private ActionMode mActionMode; + private MenuItem mMoveMenu; + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + getMenuInflater().inflate(R.menu.note_list_options, menu); + menu.findItem(R.id.delete).setOnMenuItemClickListener(this); + mMoveMenu = menu.findItem(R.id.move); + if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER + || DataUtils.getUserFolderCount(mContentResolver) == 0) { + mMoveMenu.setVisible(false); + } else { + mMoveMenu.setVisible(true); + mMoveMenu.setOnMenuItemClickListener(this); + } + mActionMode = mode; + mNotesListAdapter.setChoiceMode(true); + mNotesListView.setLongClickable(false); + mAddNewNote.setVisibility(View.GONE); + + View customView = LayoutInflater.from(NotesListActivity.this).inflate( + R.layout.note_list_dropdown_menu, null); + mode.setCustomView(customView); + mDropDownMenu = new DropdownMenu(NotesListActivity.this, + (Button) customView.findViewById(R.id.selection_menu), + R.menu.note_list_dropdown); + mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ + public boolean onMenuItemClick(MenuItem item) { + mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); + updateMenu(); + return true; + } + + }); + return true; + } + + private void updateMenu() { + int selectedCount = mNotesListAdapter.getSelectedCount(); + // Update dropdown menu + String format = getResources().getString(R.string.menu_select_title, selectedCount); + mDropDownMenu.setTitle(format); + MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); + if (item != null) { + if (mNotesListAdapter.isAllSelected()) { + item.setChecked(true); + item.setTitle(R.string.menu_deselect_all); + } else { + item.setChecked(false); + item.setTitle(R.string.menu_select_all); + } + } + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO Auto-generated method stub + return false; + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // TODO Auto-generated method stub + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + mNotesListAdapter.setChoiceMode(false); + mNotesListView.setLongClickable(true); + mAddNewNote.setVisibility(View.VISIBLE); + } + + public void finishActionMode() { + mActionMode.finish(); + } + + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + mNotesListAdapter.setCheckedItem(position, checked); + updateMenu(); + } + + public boolean onMenuItemClick(MenuItem item) { + if (mNotesListAdapter.getSelectedCount() == 0) { + Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), + Toast.LENGTH_SHORT).show(); + return true; + } + + switch (item.getItemId()) { + case R.id.delete: + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(getString(R.string.alert_title_delete)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getString(R.string.alert_message_delete_notes, + mNotesListAdapter.getSelectedCount())); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + batchDelete(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case R.id.move: + startQueryDestinationFolders(); + break; + default: + return false; + } + return true; + } + } + + private class NewNoteOnTouchListener implements OnTouchListener { + + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + Display display = getWindowManager().getDefaultDisplay(); + int screenHeight = display.getHeight(); + int newNoteViewHeight = mAddNewNote.getHeight(); + int start = screenHeight - newNoteViewHeight; + int eventY = start + (int) event.getY(); + /** + * Minus TitleBar's height + */ + if (mState == ListEditState.SUB_FOLDER) { + eventY -= mTitleBar.getHeight(); + start -= mTitleBar.getHeight(); + } + /** + * HACKME:When click the transparent part of "New Note" button, dispatch + * the event to the list view behind this button. The transparent part of + * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) + * and the line top of the button. The coordinate based on left of the "New + * Note" button. The 94 represents maximum height of the transparent part. + * Notice that, if the background of the button changes, the formula should + * also change. This is very bad, just for the UI designer's strong requirement. + */ + if (event.getY() < (event.getX() * (-0.12) + 94)) { + View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 + - mNotesListView.getFooterViewsCount()); + if (view != null && view.getBottom() > start + && (view.getTop() < (start + 94))) { + mOriginY = (int) event.getY(); + mDispatchY = eventY; + event.setLocation(event.getX(), mDispatchY); + mDispatch = true; + return mNotesListView.dispatchTouchEvent(event); + } + } + break; + } + case MotionEvent.ACTION_MOVE: { + if (mDispatch) { + mDispatchY += (int) event.getY() - mOriginY; + event.setLocation(event.getX(), mDispatchY); + return mNotesListView.dispatchTouchEvent(event); + } + break; + } + default: { + if (mDispatch) { + event.setLocation(event.getX(), mDispatchY); + mDispatch = false; + return mNotesListView.dispatchTouchEvent(event); + } + break; + } + } + return false; + } + + }; + + private void startAsyncNotesListQuery() { + String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION + : NORMAL_SELECTION; + mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, + Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { + String.valueOf(mCurrentFolderId) + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + } + + private final class BackgroundQueryHandler extends AsyncQueryHandler { + public BackgroundQueryHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + switch (token) { + case FOLDER_NOTE_LIST_QUERY_TOKEN: + mNotesListAdapter.changeCursor(cursor); + break; + case FOLDER_LIST_QUERY_TOKEN: + if (cursor != null && cursor.getCount() > 0) { + showFolderListMenu(cursor); + } else { + Log.e(TAG, "Query folder failed"); + } + break; + default: + return; + } + } + } + + private void showFolderListMenu(Cursor cursor) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(R.string.menu_title_select_folder); + final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); + builder.setAdapter(adapter, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + DataUtils.batchMoveToFolder(mContentResolver, + mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); + Toast.makeText( + NotesListActivity.this, + getString(R.string.format_move_notes_to_folder, + mNotesListAdapter.getSelectedCount(), + adapter.getFolderName(NotesListActivity.this, which)), + Toast.LENGTH_SHORT).show(); + mModeCallBack.finishActionMode(); + } + }); + builder.show(); + } + + private void createNewNote() { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); + } + + private void batchDelete() { + new AsyncTask>() { + protected HashSet doInBackground(Void... unused) { + HashSet widgets = mNotesListAdapter.getSelectedWidget(); + if (!isSyncMode()) { + // if not synced, delete notes directly + if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter + .getSelectedItemIds())) { + } else { + Log.e(TAG, "Delete notes error, should not happens"); + } + } else { + // in sync mode, we'll move the deleted note into the trash + // folder + if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter + .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + return widgets; + } + + @Override + protected void onPostExecute(HashSet widgets) { + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + mModeCallBack.finishActionMode(); + } + }.execute(); + } + + private void deleteFolder(long folderId) { + if (folderId == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Wrong folder id, should not happen " + folderId); + return; + } + + HashSet ids = new HashSet(); + ids.add(folderId); + HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, + folderId); + if (!isSyncMode()) { + // if not synced, delete folder directly + DataUtils.batchDeleteNotes(mContentResolver, ids); + } else { + // in sync mode, we'll move the deleted folder into the trash folder + DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); + } + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + } + + private void openNode(NoteItemData data) { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, data.getId()); + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + } + + private void openFolder(NoteItemData data) { + mCurrentFolderId = data.getId(); + startAsyncNotesListQuery(); + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mState = ListEditState.CALL_RECORD_FOLDER; + mAddNewNote.setVisibility(View.GONE); + } else { + mState = ListEditState.SUB_FOLDER; + } + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mTitleBar.setText(R.string.call_record_folder_name); + } else { + mTitleBar.setText(data.getSnippet()); + } + mTitleBar.setVisibility(View.VISIBLE); + } + + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_new_note: + createNewNote(); + break; + default: + break; + } + } + + private void showSoftInput() { + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputMethodManager != null) { + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + } + + private void hideSoftInput(View view) { + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + private void showCreateOrModifyFolderDialog(final boolean create) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); + final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); + showSoftInput(); + if (!create) { + if (mFocusNoteDataItem != null) { + etName.setText(mFocusNoteDataItem.getSnippet()); + builder.setTitle(getString(R.string.menu_folder_change_name)); + } else { + Log.e(TAG, "The long click data item is null"); + return; + } + } else { + etName.setText(""); + builder.setTitle(this.getString(R.string.menu_create_folder)); + } + + builder.setPositiveButton(android.R.string.ok, null); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + hideSoftInput(etName); + } + }); + + final Dialog dialog = builder.setView(view).show(); + final Button positive = (Button)dialog.findViewById(android.R.id.button1); + positive.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + hideSoftInput(etName); + String name = etName.getText().toString(); + if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { + Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), + Toast.LENGTH_LONG).show(); + etName.setSelection(0, etName.length()); + return; + } + if (!create) { + if (!TextUtils.isEmpty(name)) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.SNIPPET, name); + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + values.put(NoteColumns.LOCAL_MODIFIED, 1); + mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID + + "=?", new String[] { + String.valueOf(mFocusNoteDataItem.getId()) + }); + } + } else if (!TextUtils.isEmpty(name)) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.SNIPPET, name); + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); + } + dialog.dismiss(); + } + }); + + if (TextUtils.isEmpty(etName.getText())) { + positive.setEnabled(false); + } + /** + * When the name edit text is null, disable the positive button + */ + etName.addTextChangedListener(new TextWatcher() { + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // TODO Auto-generated method stub + + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (TextUtils.isEmpty(etName.getText())) { + positive.setEnabled(false); + } else { + positive.setEnabled(true); + } + } + + public void afterTextChanged(Editable s) { + // TODO Auto-generated method stub + + } + }); + } + + @Override + public void onBackPressed() { + switch (mState) { + case SUB_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + startAsyncNotesListQuery(); + mTitleBar.setVisibility(View.GONE); + break; + case CALL_RECORD_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + mAddNewNote.setVisibility(View.VISIBLE); + mTitleBar.setVisibility(View.GONE); + startAsyncNotesListQuery(); + break; + case NOTE_LIST: + super.onBackPressed(); + break; + default: + break; + } + } + + private void updateWidget(int appWidgetId, int appWidgetType) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + if (appWidgetType == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (appWidgetType == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); + } else { + Log.e(TAG, "Unspported widget type"); + return; + } + + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { + appWidgetId + }); + + sendBroadcast(intent); + setResult(RESULT_OK, intent); + } + + private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + if (mFocusNoteDataItem != null) { + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); + menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); + menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); + menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); + } + } + }; + + @Override + public void onContextMenuClosed(Menu menu) { + if (mNotesListView != null) { + mNotesListView.setOnCreateContextMenuListener(null); + } + super.onContextMenuClosed(menu); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if (mFocusNoteDataItem == null) { + Log.e(TAG, "The long click data item is null"); + return false; + } + switch (item.getItemId()) { + case MENU_FOLDER_VIEW: + openFolder(mFocusNoteDataItem); + break; + case MENU_FOLDER_DELETE: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.alert_title_delete)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getString(R.string.alert_message_delete_folder)); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + deleteFolder(mFocusNoteDataItem.getId()); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case MENU_FOLDER_CHANGE_NAME: + showCreateOrModifyFolderDialog(false); + break; + default: + break; + } + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.clear(); + if (mState == ListEditState.NOTE_LIST) { + getMenuInflater().inflate(R.menu.note_list, menu); + // set sync or sync_cancel + menu.findItem(R.id.menu_sync).setTitle( + GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); + } else if (mState == ListEditState.SUB_FOLDER) { + getMenuInflater().inflate(R.menu.sub_folder, menu); + } else if (mState == ListEditState.CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_record_folder, menu); + } else { + Log.e(TAG, "Wrong state:" + mState); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_folder: { + showCreateOrModifyFolderDialog(true); + break; + } + case R.id.menu_export_text: { + exportNoteToText(); + break; + } + case R.id.menu_sync: { + if (isSyncMode()) { + if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { + GTaskSyncService.startSync(this); + } else { + GTaskSyncService.cancelSync(this); + } + } else { + startPreferenceActivity(); + } + break; + } + case R.id.menu_setting: { + startPreferenceActivity(); + break; + } + case R.id.menu_new_note: { + createNewNote(); + break; + } + case R.id.menu_search: + onSearchRequested(); + break; + default: + break; + } + return true; + } + + @Override + public boolean onSearchRequested() { + startSearch(null, false, null /* appData */, false); + return true; + } + + private void exportNoteToText() { + final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); + new AsyncTask() { + + @Override + protected Integer doInBackground(Void... unused) { + return backup.exportToText(); + } + + @Override + protected void onPostExecute(Integer result) { + if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_unmounted)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SUCCESS) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.success_sdcard_export)); + builder.setMessage(NotesListActivity.this.getString( + R.string.format_exported_file_location, backup + .getExportedTextFileName(), backup.getExportedTextFileDir())); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_export)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } + } + + }.execute(); + } + + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + + private void startPreferenceActivity() { + Activity from = getParent() != null ? getParent() : this; + Intent intent = new Intent(from, NotesPreferenceActivity.class); + from.startActivityIfNeeded(intent, -1); + } + + private class OnListItemClickListener implements OnItemClickListener { + + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + NoteItemData item = ((NotesListItem) view).getItemData(); + if (mNotesListAdapter.isInChoiceMode()) { + if (item.getType() == Notes.TYPE_NOTE) { + position = position - mNotesListView.getHeaderViewsCount(); + mModeCallBack.onItemCheckedStateChanged(null, position, id, + !mNotesListAdapter.isSelectedItem(position)); + } + return; + } + + switch (mState) { + case NOTE_LIST: + if (item.getType() == Notes.TYPE_FOLDER + || item.getType() == Notes.TYPE_SYSTEM) { + openFolder(item); + } else if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in NOTE_LIST"); + } + break; + case SUB_FOLDER: + case CALL_RECORD_FOLDER: + if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in SUB_FOLDER"); + } + break; + default: + break; + } + } + } + + } + + private void startQueryDestinationFolders() { + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + selection = (mState == ListEditState.NOTE_LIST) ? selection: + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + + mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, + null, + Notes.CONTENT_NOTE_URI, + FoldersListAdapter.PROJECTION, + selection, + new String[] { + String.valueOf(Notes.TYPE_FOLDER), + String.valueOf(Notes.ID_TRASH_FOLER), + String.valueOf(mCurrentFolderId) + }, + NoteColumns.MODIFIED_DATE + " DESC"); + } + + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { + if (mNotesListView.startActionMode(mModeCallBack) != null) { + mModeCallBack.onItemCheckedStateChanged(null, position, id, true); + mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } else { + Log.e(TAG, "startActionMode fails"); + } + } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); + } + } + return false; + } +} From 04df567d896b82ca53f074c12dcf2d2d75fe2c0f Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:31:14 +0800 Subject: [PATCH 34/40] ADD file via upload --- src/ui/NotesListAdapter.java | 184 +++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 src/ui/NotesListAdapter.java diff --git a/src/ui/NotesListAdapter.java b/src/ui/NotesListAdapter.java new file mode 100644 index 0000000..51c9cb9 --- /dev/null +++ b/src/ui/NotesListAdapter.java @@ -0,0 +1,184 @@ +/* + * 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 mSelectedIndex; + private int mNotesCount; + private boolean mChoiceMode; + + public static class AppWidgetAttribute { + public int widgetId; + public int widgetType; + }; + + public NotesListAdapter(Context context) { + super(context, null); + mSelectedIndex = new HashMap(); + mContext = context; + mNotesCount = 0; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new NotesListItem(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof NotesListItem) { + NoteItemData itemData = new NoteItemData(context, cursor); + ((NotesListItem) view).bind(context, itemData, mChoiceMode, + isSelectedItem(cursor.getPosition())); + } + } + + public void setCheckedItem(final int position, final boolean checked) { + mSelectedIndex.put(position, checked); + notifyDataSetChanged(); + } + + public boolean isInChoiceMode() { + return mChoiceMode; + } + + public void setChoiceMode(boolean mode) { + mSelectedIndex.clear(); + mChoiceMode = mode; + } + + public void selectAll(boolean checked) { + Cursor cursor = getCursor(); + for (int i = 0; i < getCount(); i++) { + if (cursor.moveToPosition(i)) { + if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { + setCheckedItem(i, checked); + } + } + } + } + + public HashSet getSelectedItemIds() { + HashSet itemSet = new HashSet(); + 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 getSelectedWidget() { + HashSet itemSet = new HashSet(); + 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 values = mSelectedIndex.values(); + if (null == values) { + return 0; + } + Iterator 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; + } + } + } +} From 055b6c795f8440670689ba373407206f97b4e204 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:31:20 +0800 Subject: [PATCH 35/40] ADD file via upload --- src/ui/NotesListItem.java | 123 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/ui/NotesListItem.java diff --git a/src/ui/NotesListItem.java b/src/ui/NotesListItem.java new file mode 100644 index 0000000..a9a1836 --- /dev/null +++ b/src/ui/NotesListItem.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser.NoteItemBgResources; + + +public class NotesListItem extends LinearLayout { + private ImageView mAlert; // 警告图标 + private TextView mTitle; // 标题文本 + private TextView mTime; // 时间文本 + private TextView mCallName; // 通话姓名文本 + private NoteItemData mItemData; // 笔记条目数据 + private CheckBox mCheckBox; // 复选框 + + public NotesListItem(Context context) { + super(context); + inflate(context, R.layout.note_item, this); + mAlert = (ImageView) findViewById(R.id.iv_alert_icon); // 根据ID找到警告图标 + mTitle = (TextView) findViewById(R.id.tv_title); // 根据ID找到标题文本 + mTime = (TextView) findViewById(R.id.tv_time); // 根据ID找到时间文本 + mCallName = (TextView) findViewById(R.id.tv_name); // 根据ID找到通话姓名文本 + mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); // 根据ID找到复选框 + } + + public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + if (choiceMode && data.getType() == Notes.TYPE_NOTE) { + mCheckBox.setVisibility(View.VISIBLE); + mCheckBox.setChecked(checked); + } else { + mCheckBox.setVisibility(View.GONE); + } + + mItemData = data; + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mCallName.setVisibility(View.GONE); + mAlert.setVisibility(View.VISIBLE); + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + mTitle.setText(context.getString(R.string.call_record_folder_name) + + context.getString(R.string.format_folder_files_count, data.getNotesCount())); + mAlert.setImageResource(R.drawable.call_record); + } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + mCallName.setVisibility(View.VISIBLE); + mCallName.setText(data.getCallName()); + mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + if (data.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(); // 获取背景颜色的资源ID + + 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; // 返回当前NoteItem的数据对象 + } + From 1e66a66537b60c9af212c23382dc16d683c12062 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:31:25 +0800 Subject: [PATCH 36/40] ADD file via upload --- src/ui/NotesPreferenceActivity.java | 388 ++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 src/ui/NotesPreferenceActivity.java diff --git a/src/ui/NotesPreferenceActivity.java b/src/ui/NotesPreferenceActivity.java new file mode 100644 index 0000000..07c5f7e --- /dev/null +++ b/src/ui/NotesPreferenceActivity.java @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.ActionBar; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.remote.GTaskSyncService; + + +public class NotesPreferenceActivity extends PreferenceActivity { + public static final String PREFERENCE_NAME = "notes_preferences"; + + public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; + + public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; + + public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; + + private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; + + private static final String AUTHORITIES_FILTER_KEY = "authorities"; + + private PreferenceCategory mAccountCategory; + + private GTaskReceiver mReceiver; + + private Account[] mOriAccounts; + + private boolean mHasAddedAccount; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + /* using the app icon for navigation */ + getActionBar().setDisplayHomeAsUpEnabled(true); + + addPreferencesFromResource(R.xml.preferences); + mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); + mReceiver = new GTaskReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); + registerReceiver(mReceiver, filter); + + mOriAccounts = null; + View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); + getListView().addHeaderView(header, null, true); + } + + @Override + protected void onResume() { + super.onResume(); + + // need to set sync account automatically if user has added a new + // account + if (mHasAddedAccount) { + Account[] accounts = getGoogleAccounts(); + if (mOriAccounts != null && accounts.length > mOriAccounts.length) { + for (Account accountNew : accounts) { + boolean found = false; + for (Account accountOld : mOriAccounts) { + if (TextUtils.equals(accountOld.name, accountNew.name)) { + found = true; + break; + } + } + if (!found) { + setSyncAccount(accountNew.name); + break; + } + } + } + } + + refreshUI(); + } + + @Override + protected void onDestroy() { + if (mReceiver != null) { + unregisterReceiver(mReceiver); + } + super.onDestroy(); + } + + private void loadAccountPreference() { + mAccountCategory.removeAll(); + + Preference accountPref = new Preference(this); + final String defaultAccount = getSyncAccountName(this); + accountPref.setTitle(getString(R.string.preferences_account_title)); + accountPref.setSummary(getString(R.string.preferences_account_summary)); + accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + if (!GTaskSyncService.isSyncing()) { + if (TextUtils.isEmpty(defaultAccount)) { + // the first time to set account + showSelectAccountAlertDialog(); + } else { + // if the account has already been set, we need to promp + // user about the risk + showChangeAccountConfirmAlertDialog(); + } + } else { + Toast.makeText(NotesPreferenceActivity.this, + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + .show(); + } + return true; + } + }); + + mAccountCategory.addPreference(accountPref); + } + + private void loadSyncButton() { + Button syncButton = (Button) findViewById(R.id.preference_sync_button); + TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + + // set button state + if (GTaskSyncService.isSyncing()) { + syncButton.setText(getString(R.string.preferences_button_sync_cancel)); + syncButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + GTaskSyncService.cancelSync(NotesPreferenceActivity.this); + } + }); + } else { + syncButton.setText(getString(R.string.preferences_button_sync_immediately)); + syncButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + GTaskSyncService.startSync(NotesPreferenceActivity.this); + } + }); + } + syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); + + // set last sync time + if (GTaskSyncService.isSyncing()) { + lastSyncTimeView.setText(GTaskSyncService.getProgressString()); + lastSyncTimeView.setVisibility(View.VISIBLE); + } else { + long lastSyncTime = getLastSyncTime(this); + if (lastSyncTime != 0) { + lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, + DateFormat.format(getString(R.string.preferences_last_sync_time_format), + lastSyncTime))); + lastSyncTimeView.setVisibility(View.VISIBLE); + } else { + lastSyncTimeView.setVisibility(View.GONE); + } + } + } + + private void refreshUI() { + loadAccountPreference(); + loadSyncButton(); + } + + private void showSelectAccountAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); + subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); + + dialogBuilder.setCustomTitle(titleView); + dialogBuilder.setPositiveButton(null, null); + + Account[] accounts = getGoogleAccounts(); + String defAccount = getSyncAccountName(this); + + mOriAccounts = accounts; + mHasAddedAccount = false; + + if (accounts.length > 0) { + CharSequence[] items = new CharSequence[accounts.length]; + final CharSequence[] itemMapping = items; + int checkedItem = -1; + int index = 0; + for (Account account : accounts) { + if (TextUtils.equals(account.name, defAccount)) { + checkedItem = index; + } + items[index++] = account.name; + } + dialogBuilder.setSingleChoiceItems(items, checkedItem, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + setSyncAccount(itemMapping[which].toString()); + dialog.dismiss(); + refreshUI(); + } + }); + } + + View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); + dialogBuilder.setView(addAccountView); + + final AlertDialog dialog = dialogBuilder.show(); + addAccountView.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mHasAddedAccount = true; + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { + "gmail-ls" + }); + startActivityForResult(intent, -1); + dialog.dismiss(); + } + }); + } + + private void showChangeAccountConfirmAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, + getSyncAccountName(this))); + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); + subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); + dialogBuilder.setCustomTitle(titleView); + + CharSequence[] menuItemArray = new CharSequence[] { + getString(R.string.preferences_menu_change_account), + getString(R.string.preferences_menu_remove_account), + getString(R.string.preferences_menu_cancel) + }; + dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which == 0) { + showSelectAccountAlertDialog(); + } else if (which == 1) { + removeSyncAccount(); + refreshUI(); + } + } + }); + dialogBuilder.show(); + } + + private Account[] getGoogleAccounts() { + AccountManager accountManager = AccountManager.get(this); + return accountManager.getAccountsByType("com.google"); + } + + private void setSyncAccount(String account) { + if (!getSyncAccountName(this).equals(account)) { + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + if (account != null) { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); + } else { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + } + editor.commit(); + + // clean up last sync time + setLastSyncTime(this, 0); + + // clean up local gtask related info + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + + Toast.makeText(NotesPreferenceActivity.this, + getString(R.string.preferences_toast_success_set_accout, account), + Toast.LENGTH_SHORT).show(); + } + } + + private void removeSyncAccount() { + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { + editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); + } + if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { + editor.remove(PREFERENCE_LAST_SYNC_TIME); + } + editor.commit(); + + // clean up local gtask related info + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + } + + public static String getSyncAccountName(Context context) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + } + + public static void setLastSyncTime(Context context, long time) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); + editor.commit(); + } + + public static long getLastSyncTime(Context context) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); + } + + private class GTaskReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + refreshUI(); + if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { + TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + syncStatus.setText(intent + .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); + } + + } + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + Intent intent = new Intent(this, NotesListActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + default: + return false; + } + } +} From a425402a901c04109fc07d54843b2cb1b828bbbe Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:32:22 +0800 Subject: [PATCH 37/40] ADD file via upload --- src/widget/NoteWidgetProvider.java | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/widget/NoteWidgetProvider.java diff --git a/src/widget/NoteWidgetProvider.java b/src/widget/NoteWidgetProvider.java new file mode 100644 index 0000000..d6eaa94 --- /dev/null +++ b/src/widget/NoteWidgetProvider.java @@ -0,0 +1,51 @@ +/* + * 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方法,更新2x大小的小部件 + 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 + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + } + + @Override + protected int getWidgetType() { + // 返回2x小部件的类型 + return Notes.TYPE_WIDGET_2X; + } +} From b826f8d9938b7421be85a2fe6ce7f6817cb02966 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:32:32 +0800 Subject: [PATCH 38/40] ADD file via upload --- src/widget/NoteWidgetProvider_2x.java | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/widget/NoteWidgetProvider_2x.java diff --git a/src/widget/NoteWidgetProvider_2x.java b/src/widget/NoteWidgetProvider_2x.java new file mode 100644 index 0000000..adcb2f7 --- /dev/null +++ b/src/widget/NoteWidgetProvider_2x.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.widget; + +import android.appwidget.AppWidgetManager; +import android.content.Context; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.ResourceParser; + + +public class NoteWidgetProvider_2x extends NoteWidgetProvider { + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.update(context, appWidgetManager, appWidgetIds); + } + + @Override + protected int getLayoutId() { + return R.layout.widget_2x; + } + + @Override + protected int getBgResourceId(int bgId) { + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + } + + @Override + protected int getWidgetType() { + return Notes.TYPE_WIDGET_2X; + } +} From 2d43e30c061d6ec6ea633fda01e1598a96672f10 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:32:39 +0800 Subject: [PATCH 39/40] ADD file via upload --- src/widget/NoteWidgetProvider_4x.java | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/widget/NoteWidgetProvider_4x.java diff --git a/src/widget/NoteWidgetProvider_4x.java b/src/widget/NoteWidgetProvider_4x.java new file mode 100644 index 0000000..6b062ab --- /dev/null +++ b/src/widget/NoteWidgetProvider_4x.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.widget; + +import android.appwidget.AppWidgetManager; +import android.content.Context; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.ResourceParser; + + +public class NoteWidgetProvider_4x extends NoteWidgetProvider { + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 调用父类的update方法,更新4x大小的小部件 + super.update(context, appWidgetManager, appWidgetIds); + } + + protected int getLayoutId() { + // 返回4x小部件的布局资源ID + return R.layout.widget_4x; + } + + @Override + protected int getBgResourceId(int bgId) { + // 根据提供的背景ID获取4x小部件的背景资源ID + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + } + + @Override + protected int getWidgetType() { + // 返回4x小部件的类型 + return Notes.TYPE_WIDGET_4X; + } +} From 96ddef32098ad4ed9619b833561edc54d0fa3ab7 Mon Sep 17 00:00:00 2001 From: pq95fyb3a Date: Sun, 17 Dec 2023 11:33:33 +0800 Subject: [PATCH 40/40] =?UTF-8?q?=E5=B0=8F=E7=B1=B3=E4=BE=BF=E7=AD=BE?= =?UTF-8?q?=E6=B3=9B=E8=AF=BB=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._2023317150_小米便签泛读报告.docx | Bin 0 -> 264737 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/于志洋_2023317150_小米便签泛读报告.docx diff --git a/doc/于志洋_2023317150_小米便签泛读报告.docx b/doc/于志洋_2023317150_小米便签泛读报告.docx new file mode 100644 index 0000000000000000000000000000000000000000..7bd3838406e4fe0d7c39b6f2a738a00a120ed851 GIT binary patch literal 264737 zcmb5UbC732vn|@jv~7FZcK5Vx_pfc+wr$(CJ#E{zZNB+V+#C0v6E9xGt3URxs9HNC zBiD|~%2g#V1q${H=szt~cK^?R-v7Hn|LYjp8p=D^+Bwq8|AV3a8-V&R*g&YJumvy> zkShof5aRz0)3>vubF;R}PUyGUV1Ny|0e^=NxoI(Cm(2)Jh-<$vR|g%`j`1gCu9pWO zlQx@e`#dTZT|2XX7d_r#P^tUEdf&(JnOUlu$3L(L6k1hInJ4$K$gO#21X0RRds^Bh z=P(vo+IxZmZ1LX97)gT|^pjl}J0OPx&h6reN*nUW=iUWG6TSy8@5<2lG@sb=!mAFw zrf*3K%03q5>Z?|@j9~;BVcLKz0MsK{i7YbRhj0OhkQDv za(=9j<&!wQ{kzIiqTL=IE1*tB3VCHo*LK6&x!~1}^Tg+6bF(PYPy-CIPF9Uvh=rIE zh=jU$icVQv#q%qfp3x)OO8D;6Pj2)k-y)^{ve=G9Yz5o(` zcvKuPM~cAr1imHuIxE(B8j4{!lCTYA-2@YJh%f_?$vKcW49Uz))Gh1S*9!RUW1OAhFB!jW)5KrF^U zKnVXE;^^dVW$gH$$RBCRIc`cK^(qay%AbKjj_BOH+ZsRoc0tbah+(XM*mhUVc>0~f zA~^4Hkku}aOBn&Gf(qGY2V#Q?xx0age*>6(5YwzKN?$YQ#aFQlC#ml6700lR<&j3^MRY+MoyS721F%yiSpedyKNFgBg3L9)Ib$G zI=KGc-p;RGtSj%GTu>@Dpa>7hLbK9wh*x6E6$4iB{T=Rwqeb-=06q~^Qv7Fg${d!h zu1Q^3L&03CZaptNQgSS?90@@}i??+h!fNQAVM60iB#2kS;QVWluz9r!%Nb<__cgTB zW{uT1of`OlA@5yp2wov?;!wp#yA!y^Al@LptrWeEXk*sl{1hS(Txa9HmqrqeyMUzP z1)y40PF3TMR6KbNt~PqO^L9aV^d({U@kx8!fhm%RL#;6U2bP%Gs2_%lVxo=a)z~My zQp%a^yUV~_!>v+D5ln&Q$+i$I;4Px2tCfamw!Ly;=%MNKCpe+NOGjAmsg(cglEC+e z%$JL@L((OecMI)xX(t3v!#dfk!k7h&BIrCC*Dx=ecERENhF}0F7-Ie{Rwvw^^{Eo) zW?lq!o*sk6TWuC`xWVH#gnUi}H6$t4*7;`Z;)2J3GpeWD*P*~hr(riIgq;k?vAQd9 zpV;e-C=ZK(0_fx5m(r<53s5l8gju!5CMEXmzNjSftnW~Dthd>_JgR;Vgs}j$DU)pz zoc!N=Yp3fUNT(P^F5^&DnQT}!dj_qcJv%h9zbd#0YWDM$1z1N8pW+n4=HmQj1yeK& z1TrS+-)4*xkD;L&CH(l4OkcoyDQ7Qj8YMeJCRm+vBLP4~a#HA4dvh&8Te;TIo$MRP zF1jsbmpN8at6!@rb)NOK`Y-x`f$RR`vZVH5Pq;v4+U((nta>J6vM0&+ewlth1IzwBkp9f$4C%!)llB0+{H1{j9)8YU7``!jf0pcV+~{5qaB<4OWb7aZ52R3`lew*pDTiu8+MjA;qezM6Qt03$!f{o<@ft|2_Do%S~$Jcrr>TqbM-gYDV@#+aJd$sF!M z=b+lTEv7NeXeCCY5MGxClX*%^BCe+v&#dx9P-+a1u zry-K*%y54@2htp7CP)Es#J{sV3fDiLx8o)yZn;9DU0id!H~rz>S2y?qS*f&X8+%{K zvb}f4RwFmqQ@0({oJ(V&1rh6GP{K~)No*yk&^zk0;{qP>i|f$!Z7fBJJ~+Lz7(wWD z-&5MF5w9+ZM%RL89pR`M@22yLzB-)RrNnpQNFB3ypxXg?{%ACRStMq{-oVF5dX{gj z+hrAl_o^H941=|4OmED0*}jnd7@XYLt?TtyaXE1cY^aQd7Nb{+Pu-<=r}o(ZS=y1* z8c}1(mx!5LCAJmaG$wCeScO_nI?#ohTF`|Vll#UA=<1chH1lRe+=C`XnP4>=G=Pp* zNO#l%E=ec@4+$uP4sj@hcQL2}?vb9#9lT4Bxt{h~q^6rYSMZ_phM%SCm``IMD1j(~ zV8Y>8LBsrtVD)b;2u!F>@aZ!Y16-(1Vdd~@R29SOUxiJ(i~F- zTa=blW!5+`_e(`4*6q_`VYBVQhE5sfwNsStlH!Jo#!ju2-I>AqJAW&7nqEks99vFp zn^fA(o9U7tw1SlR*sQ?2^7WgfD{AJz8=HWseI*eh>hZkYwES{37ZT8ICiybFB|rS0usOE^Qtv`DQA}-`4P=Jc^FU!g#}qN zD}oqMCxk^r-W~=LziSLx*^H|6F4D7n=VSu>WRH6r??#9UV;Q zpXVs-i&^XcCvq@^l=RD0sKXQH;T>LfpXEXHwk1BQ-tv=j!U9_iI%qOzASehu=*0tV zxIfBJIqrn=o7EaQQg~X3x&+lTS*mi^V+)Y-Q&AV{B1C{Yl19!*fLs?s1r9Sr^Zg&Z z)tbRPL7dcSc8iAahgc>4aK#keTScob@B(tc4ZVs?%W*8dN;VrE7AhIPd^u{4h(0N8 zZz*T3HF-w04hbs31#GP=@>S;xb5NTIT-^jB^cV8T86A5D@8?MBQuEej*oh)h(^u;}`SNi%+@4xv ztV%oHQI)4;BlYZWu(u&HWpt`RmH2qS6+ocqgY8saqe?#)Qz$~;IkirN(Yc%`@!{Lh z1{wVhBpSE!9$X8}+toxUS6I+lieLKs%$_GBWvzPZb!F{}&D(oz?=JX9P&>1^)74e| zElpk?F3U2PxV7E49Cz7y#}rcZ)t(-36WVPoM1MN=GS}H-YkAnfdtH^C#|-AF0|3n_ z!(KB&4)zHG-x>DOCo_x@rY8x>n$ooXQ08@pGx~s?^u$Q90oE#DzhED=a1vw>lGxaQ z>%F|apGkEwoMf{D!YX7J^=^%XW?;QG@xQl7X99;LAz00!TmrQ9^wVmuL#6?gAQHRawpDQo!E3y45_9o0D+(S}^#E&5hR0=yWW=z{4(9mw zTK+vE$*5Jj$kDi7vgm?kOuM*<4RVQ#H+u1eW9pIg1{gJB5T1*1qEY(k?i&58Cc$v# z)#3c*k_NS3Yoze@ckK{JE~rp1#HXQTGShCuE63B(JrEOOQG&&xszrs3ZQJRFL*YRD z_0Kb|`>K7>G0Ck@?z;QfST^q${Xi!+5d{gWwGD^af&W~+^t1t(e-cz^G{ti)xdqFq zQxpQil1=k<_*j2Ec1Q;WJUIor6vDp`Ae^{jhCk4yVqg9ZK@+Y`tnZQUL47^@3&^FFl58KDwHbJ0;VQ+>)> zVXr7{r99~3vaUf<1}qC}IsZuvJL2U=5=s-7txVjpH@)1xH|5EpGZJfssD95{;33_U zVm%ufZrX#5@DRFtd1~sHZFI1dn6EmA#D`x>V^MU9tpe?T5m#G4N?Jpzq7w4(Z`O+N z^+!2T^4HLE?d=+rPRiTGdeLz#A$1|q)P-yU5j`B~$ZLg)ynb}85^a{$l;6Z$=Q<(F zq&ot!(Vb+t;67n64~0frd^qukkU6V0_tJf>OtYMI zS-FB~@?NPbGw|$!z{Vc^4e*o^B7Xq9xVa{Rczo*ezcm}iQk%T;pWj6cN0|OV{P(Ka zgJP+9@Lx^G`A3>yj&1wM01rAi7o%AmRyx!LOLqHZ0LktR9 zhm4*C45)2HHH(Gy7zs7&RqYOmZpI&xQ2(`3$`hYAC$Gozsn!OLcGHSuMQJ>zV=fA5 zBc_vmlXETI_=S2$Ma$m~n&SN>)1Z~v?6p7N7b?u<>C1$wxqi?Ja^qI#mrXU~g$mhM z>Jk^s8jQe`)yiZ`w`yBak`>p_OXlPg5f(jr;426ummT>Fnej@dwSz_M_L}$7W12S2 z3hAR)DW+9FSS*XRuU9N5OdF}vr^^T8^&kdu1>1KO_*KchmT-!jaH7|`lKVY05bY4{ z^b*f!Y>G#6*uBU(3S~)?>e@nq;{{T!MOr-R0^iI+a>aasim_ZfB!<5h<2Q2P<5z~5 zy`@?^>2&U+3o0y`fp#e)Dr8@pQ;xDM-i>O_Ga5@OW|-z6?H5@Oqy3!a9c0I??yp6~ zMcBdn8U=bDu$ z%o%9U!HNe`{u-cTBjUdlDS4rF`_Y%gg>fcci}{@)(9|8R{HxlCF^y?O4wx(E^3mg} zn%^pr5?$yT_gtv&0JnpQu@ z^fHuO&?Qqr(lj4wiw2we6l41&R&wTGhQTM~P#yD5qeH$|Vdo()>0TAG8O}YiR9f`6d5n zE=cD=EG6s{q+yq#VO{o10E`~IFNkW&6SG+j0gWU|5KUkF$ESmy)6!G)mkuqb7XFj$ zA<*5=6xNPSItXNzMN3fU-(;(GE?2c@xv!WE2}qPc*S3+|vI>L|L&{`iKEDh;fdF2u z(b|)ttxmw0+DVbtmX0rgSEukrP0BU6H$~T`mLOHly`HPLO`Gqy^T@OTpzXc4;ajSt zq;uF0>L|%@vMOT%Ih%kZy1w(=Pmb=IC45BJ86sm>bAChLht0q29R<_HrEx`GNf zU=P1q_KB#Ve7c=nC)lOAgbJUhOk4Y+*=)DGnpt%m@o(ljn9UAWy^O;6hSm6B)&aY_ z4gSHmWgLLgu*54hI|h)1Da|ySV$Uc9RfWZd69g}>BIEiC0OS%$)99EJaLL zonb<9ND5}HUsK7(g&y}5b8$c`vJXd|pgk$s9z&LswID(c zk_^sYF+_FsHOllr8DiL3mpdm3+QAr7(~+;b`Smkt*F+KF{A$RQ+Hv}5udl~}uC zprU1uSV$~(FY1FV%d%SBii-5_)vv2-Fr4=2#Y!tJ#Ve#US3q2)h7{dsb4YmT$Y`{- zY*ANqcC6cwqNGM++mECGn8=x)Vn>H;=Oznx>w{gp;iQ=#Y3sz4xrqG(J zIK#XgL&#(gT$YZOnZKs~eQ5b=Z13kqXyth|b-NbD8ggU{xxeTxY9Zz3_R#zN|Doo= z{O={-$;{Z=_DJ%g8}*X0I%)9wIfhn(f$f*RXa%8uRxfDYBivV{zc5(3#&?J}-q6 zJrtypAi^2}nct`|Kj+J#1X==_OaqM1eSAonWoKfFC7G6%22g*+wG60ach|!1FeP5c z|2F#-lHB4fG(Rbpx1f_Gyy^rP*m)YV5DQ&)e_p+;?C?Lgnrw5UmWUV0g9?p%jNq6tciL^|9c&XpjbfM1ry-bn2fii^9=K1#jM=x%g0gs?f1{8n4VcG3Py-8h2i zchjC3`zyd`UaVNX*aGu|{pX0OM(Jjet9c+7B5w~-HN&3KTh$et`^qAQ_0O7E=#5Kv zw?fNp$WJuc^oGil?H()TgRaJaYbR}3LGVf*tgiNe+V7_))u91P`H))Ly^G>2mtvcL zr~*4rxj>hW$LYWk87`(01L_rDH%qCa4i)}#*Ps;44_})@ucAt zrBwL&L`n8A3QW6$8x;DaODtw6BnR$nET~dK7NT;noD#+hKcpgcjK?}VJ+Eh1k*3>s zfhQvfKD<7!x3B&5=_C^t7-ae8PsgG3KkoPGFE5QZ+dS{<(HP|4Gr9b}ZzF0pgjNNd z*=jpHUl(r+YO%d;Uk4 z5~n#?a?+c$LGap+t?~v(t;`Yvyx|kZ)C1^KnW$vQ!5sbbN;9jNh9l?K$Oc1Cez2S< zR;1mXgBt>aSP6Vs>Psx_>tu|F#^=OPZ!HFnb|V}5i_Pb3d`P zv6#&9)@(K+l(O2Z_!7h@AN80*;lwxOiFB{Pjjmzu@a})c#+`PERwZA?EbMTa7K&Kt z5uc0(@Y=gV(p7r9sql!ZiXn=&6fe_ADaKvd{=Px8iyu|m=5n~{Dvxa+54M}SU<7%f z*(l@yq970VD;;rU5;8H_1|1?j=%;Un)_!&(A1c1Qz7u%V^byURF(+d--f!`0c|2pG zig`JU-E>b6TK%?Bob=__&LS!3t_{q=2$p0+plP&5<6G`H5_6HUo`{nM=DIJbDe_0P zLkFbN{iqA^UEWpQxWWms&!G$x}4~hZn=gEzj_21|io2 zb0rtH&$~}5_DIpro_v^mtkBa$lMleaaR;Ad-jdf+qm!$qqi$9*S|`>|%9Ifl6MbPo z$_F}GDi@U#I?#69N-JF~C>4w!8OT%U{nVwI&akWlEJ9?YNeZCcl%1`xRmd|5C9!=W zX>IKpW(i(FIhTpB1Yzh2jWxpgq8m%J$61pW(ClhDF|me)4{jEc+Pqu5iC~)*unId; z*9H=5sXM^MB}a{GMM^lYf<_&XQ|>Tk9WBkRjaarsTMXa2l{B zqO7;*t^&bC1io<{*I)q!sU?LW%3RqIG#ocx@uDcRq{I&zc;6E1h=8_4shbZ=9E7KZW$~M%$m8>EXFVM>!k71*Y)!v#e8{O4q`c8Z_a{$*ofEb>4f+ z=U81)@Vkf*8X7`9hDhSP!D30vaq6v#^CaWp--5)7TyqwG04)-oQwE1yURfPqD8O1I z%q_S0{dJG9)3mru%0h*mFkqp~r{F0gyZCLo@$<#+du9z+-hUm2yJRsd%rdu*1CvvB zHQ%IuF;^I_mx>#YEJiHJP(!{5eYbw6km~Y9sYrTkPlrG6f`<6>ZjLEthgez@9%S3xom?tdQ*xKI2Z>#T%Ggv?)uUx{g z;0~2fWm;Gg_b)SNE3ncvhu+zA`wwSq|51XIiCbqIt#M&>ism!CoC)UmXe-i&nPBa% z=2d(Zw)VbK;Ch|O^CpB+pcpwjRoEP{)qfePzTq!Zp!KipOe%9JP)$Kz9}6PZU{}X5 zw|*Z`KVC4k5Hh5_BR^Hp1bBFpFn8wMXvNQ{Uoj^foGo5VPT-{L+8kNRTQc+UGoL8E zO{2Xq7`|5f*sdeT|82LC9{Ixd!E`4Djk_&N@%GGZTHN_YoI53K5fo6P4NZ zXom>)VM3ngcX|Md;iD2BsH+ey*;yKu9uExI?*bRA%Ri}>JIQM{Ii0P=saFM0g>i8m;wY|RXef{s(aG$Iypn)CVa(@jP%u>fGuhD$H3uonWiiq+4LKm zq7`yx1k(uI2|rdn$)pf!8dtv$oHsou%DW(!lxSca;FFhfIx6^h{jGG{guQMK%cch` zwclr3ttjSGXX$-{1q-jazlDR5r-8)#!*Gd*@ z)}wVfqvFKnqhQR>tB=3OQnsYAW0QxBvt>VK2o|!<{jiRBXX*R6atirzT77iVf>!Y5 zzH^r(o14ifw>+%fjzgEYfhU3%`^?IDUI7?WRVAt+v@!Om<3Cnt$=F^H2{jwS_#emx~c5XLeJR? z_%%C6sYqM~^r)*!>`-vrwFz)kh!$5z26o5^3L5t$hoa5&1WiP}qEiVPYrxB^UUg(# zwSE`x70XoA#bj8faX2C48Uqt}z@?)!tHPQx9g!m8M(GH+E}qAe?*!8^{cU{{=7Uwj z?e{kzx0gPR;O1E28l$W_hOeO?AOE zPYWP3$uph&E!O+?K{k)qqI)QO^;Bakn@!QZcdY&}{CDz1nJb+IpBA&w(c|8q-*=bK zf8>5$0~~+Ca*;H|9D#c`?#D?wU?|;{=ba0RszA8YI3^$h%;;@UwnnxbD5!sfnN~EH z3`Ywd3k{yhii#w%f37*u0cb&krlvVtjg@nc{)o}^VP^7g2#8h28p;af^jqJIcfn6P z{i4FRZ&gg1Bh33c$%NRumB!6am9K~XWB1BU?Cr#-24oU4kN^-^YH24+sjLEg5y6H)88E`pzc(8_$y=&uU8)Kp1cP`biLO&mKCp#+uhMd4 zKU=OszK+z)e7mqrf`?Hj3+bkg_zsAH+%W3EZ=VJ?K{Dd2L7Lu=j3&_|@yF?kJ=iLL z_#!k><7yS-ZUnWi1U9RIJgo?z`-Jm}Xj!}?UFEN-VH7+J*V`CvqoPY!a*qo5Vjl+( z^g)r?@_yF=Uy+-M3GcfJ<~U{zm$FjX5BbW}Q3EMC7akk#%-{$sYH4w>M0I;iR>G*q z3D?Vh%{wVzZI={n2dJ!%&MSj3L*uFgLAhiS%&|JkQ20h*9$LK=O9G zuoI+!%tGN1fk51%Iw0q*N&f)J$?97})ExjYvych>Mh;ZT6aArnIb_vzGS!9U5wdAi z(hB|YUq7f6fzy4@tz&>~Ycc)!2>FF8LD0GH!wBsb4V>O?4i;mih`_{OOXxNHi!L27 zQ#D0YMcRF*mFr!AFeBeXU8HT$L+UFG4e|>k!H!79ALeZLLJ~=d*g&%7nEk$f47GIz zDoj8f9-i2s4j7*ECQDs`baJsYVfawY2aK>UO`VaCUvQt~U|P(GZ)t@5l?n*t;X2^~ z1rTFj>lh&@onTvK0*O&YZvbFA8byN$;bmZ0{UANyAHT*;d6JEwb2MaP zc7zNm=+$}OaQF5J&P;yv_wlwF+H^CKHH_ubNU_6#Oh9*JXhg;{TCuW@(LE+YdnbNo zy2=vM3aSjVLqn<|9#AP*4k(S|2(3LXCuVR{l?W4d<+e3!P+=_Z_pb9FyQag++4Ms^ z0?3;L{T0Oil0H`@cg~FPXzNn`B#wb=>y#t})>mwhY6we9-ooIlD=np!Ic?-&r0!fp zRTl5yC(!l6av2Ka<*S-se^Co`h>Bo+pU~RLBiCoF^c%V~iW5D6lz?^}IogVv)^E(e zny}_?F*4~358gYqM1Bbu$3+S!A-0eB{cJSDHo)IhQ807Ao2NZQ zqQdaw5&vgw`^l?-jhQ=1!bbj@W+x6s^=x+ARBhzjp8gh%^Lp{4Vc<^th@;x0Vfp#Y zg)E3D>C&MA|3gIoFNwT3xcsQQhS1mvHAu)I_3NZB-h2Rh z6UGtLZ|qlk1ACe8HXh)!-#}-wiJ-@a#g=nmbl{MUo2 z@seXg%GSBBB9w0OpeTB6{oo+JoEEFGas) zPF(A$ZYGFSCW2#V(L1Y2Ii<1{XH6CEzYT9kc+-l6sFkOBd$k`V%NX3!0$ZEjLPA82?T*?sfZl0 zBE1Yxx_8>2$@S~W1#7`Kx5C!y*LncCi9lCxp4yhi5DvS{)m_Em*^}_0Vhp|W+bq;% zE|H;MGJTEN>8A9%SO_%iAMbc>!_`_jQ)UFmRM5y1g_C*fj$dDR-bMq_wWl1B+9#dc zKx0wi8)u6J-y!qV_Mf7VYs({>!&fxY@4LusqMD}K@wYqBkIQStYcOc>%Ko7XP^p%z zomwJ?sbQbvmLre-iXR}|@u{~7xHk#`#cj3-fJ1kGq4UO6}^r{z7|+Z^RrySlv!6F#VVJcg6?R(c2oYjt^9_Y z&m#;QI4?TknZVT15k_}YUjM9{e0 zI&Qge7kmg@MS&N@u>@X?#>fiOcA+v$rcB+PL`xlyS??a;|LOvG`H36~gFdjND#a)4Tgq3=qP z-N26EuI1h)dkZeomp0=shQ~*UJATXec|f->)dN-Y*&oj)y{PH4OPbmSL3;tKz-<0j zXisI!{f;`Z_PW&U;DVQ+QKzfa$Wiq3XNMTR;BfJ@uw7QpL2}}XU(|hY$tUYF$V>#Y z9^%qMo47_x9-fXc#YS6y#B|&IHR;|vbEC^!hiAENWsZ5V;wk@xS?e9J6IZOcF+?CY zg1m%{=2^tTuB-6esImQq-b}nDpGGpt7BXAb|CpgOw{IH(`$W1eRsO}W0l$;g%#*3J z@kgD-CG@N@mYHCuIMvtjAn(!PJ{Owxz4h65Tp$`!mICu9UlhBd40@GBFV>IOfSOOl z-r`>6%1HezN;Ix(c>4@<&^4a2oHLGM#|nXDSyd4D932w&@lj zZu#2n_M7yzYGuK6H$D+pO-MyIG^O$JoLVq~Dznfe>V zuZbg2WLXx2_7%m6*DKrLXVrKp^Vx4HrL66odNN2o2W%^JmEDzzW{8C1uY*>hhb?b} zOLY*ZqCD9A@p=K~s8&3jF&eBP`e^i|%GwC^8YH{E-#Gl{3FUNdWf+b;$i&+Qeh=c$ z_iyf5{BPCh&_lO?FGa)W^UBpAo+P;HMbGRwL)Gy7Rf-J(X0oj+VH8R6@A}1@hm9YF z^(&U&I`^%ia$|e9oZ+!(IIv|S9XwLTk(+$}f>6=QtTNGgx_mDi!Vl(tu2W~QKWDen zKD78{m6nqW3ptJsT}mxfq&zl81>0z76_EbFVmF8$+Zb6K$t%n-j`FG=m8aiH`(T5P ziJ*BM0tvu67F))bg($-)HOF>d6 z4`QMNneY;!3EVPbYA2Fd2KdN`)1e=$FEy(&PkZr1hkL+IlT7`ay@?%i} zHSbX>s#`y+bqA{}KY0opP;5f6DZ8>DA+VvA(a{qE^dINqEPxU-jKF^)PE#xFN+p>F z>mYd!+#lE>nPgFbb#0;NE_c*^{k!R|7awyOW^Dwd+w6^@W=Pb`?jalF0%LSQq(qNA zwFY?(-msPqGAMU;#66r%2C-YgTw)WipU{4Ezy72NLJtWCxc*- z;Yy6NlC&w2h$_Sg8f0p9U5u2SCKx2hBy0TYeb>;@gZx7ekC7FM=G)LY&bRd*pPb;c zp6hVNZh(x;fz7kfZcz5O+O<9bxE{UI-I&+jBb5VN=P9|1BHlo@vhvKuM;x(BaHAC@ zv6)*ds-7s0Dg%uV6k^a_z?CBc5Bt{$4{p~)h zwfBlPA=XI?Bi|3c+G@4#LI*bp(r;xX=Xdx+(k0)5C;y+@`;u-UzN)bxM}mAL*YOzYdWzbA(mo70^&K{Tvte1!18Pl*}{D zfW$P}}T$lq4V9;|#B54_sJd49sSu^J9)g}t@8Tmoj+Qfx2 zaWk*DB$l%(z*1l%OD>mt#4b{MYdgGO*^RNY1J6%rn(~L0mBGbFEHA8zs33eaBWn~J zW>>oQwK2&~_)UN?-doOS=V76%eV~MaRYM$nQX5<%^3s#|L&rQuNH<7>C88ZMmHV&_ z7jC=&zJxMS9{v|YFY3l!QEV(`qiuNrrxXhfaj>;%ei&-awYa`QWLK5NSconumY+M< zk)lf>YQQl3a2s^uybY`a-|XnIK)^SK`nSjT$h(d|1^Ny1NBC4UjD0iYQLIQnc3Cx@ zh@T@BDxtZhbgWTsFO#SbIvHM&rF7fy?*I#{Xu(|W51n8Bj00uLVn46g`mN9$5jgmJ z*wlfRlaYqso!=F`{6cM`F4G^Dj<IKrIDA8!nc)Z#Sa;mOow{{+*a z={QMW%O8}vW0lpCd{H|SqP6|g#f0rE^3$^m2rxSTau7OO{=V@?yFL8gcXDc;f?`2NG$7iBTpcsg;&ngK8X=z!Y2<8>_ zG6uUAXLF(1iQ&aP1CahTC*5XkC1O9WE7>=pq*YS7N|lhN+v`;kmDS#PN$JUig|*M(EaE(!u6 zk#HY6Y!@eZglvwK5D2D6)iXL85@qAgj@bU&crA&& zFHAJz^7c^<0Sni(Ot9+|JG3+VXSu6DBx|g!&Mqz*l}n#a95~BZ9k!~aM@L7ug(@G{ z1N9(P4r;PHn|Q}eAF57YIioKW2%27ZPfv2I>yW8yWRJa$6k}7DFnhacpGsHgrIYA< zIhlAbok|l#3DfDTzwn>MOVK&E7x!0wZ$ z;5Zx70gypf+wKAo>1`_)>0N=%sSWMv95x6>jRcJ_Yg$28HXMpd3*{2w)R2PTtV`hM zATlYRl?Y8>=NB;XzFN|6oAKN>m{1wC57#Z_GT`f#Eh3#Gu2Pb6zRVO@h3MlEUPGsMfq!v|%U z3Mk?<^0!c>#O+iD74$VI*{4T3B(2&Pi||AoU^IJl4Vx0>$d~ zhKp%B+=khWqGJa?p`~d5Cti0@xpba&*Y^icUvT2jqR;C3lCnm!{@)vhONNw=)NqPt z(&-n0pit#&3dJ`W!!4xrf81}wW~S^;v-eBW?L<0dKb;gTC|tEFDfT1EXI;KultFUc zMyBkz*gT+KzlPG(XO+eZ-*+c27udy^Z_4kX5Qw(zmD#CUgu=KSH9IA=SO#Gx@Sq~B z%$t(DpA@|j_BUU{?idGvOg?-V*=;&aOWWCEQIXRM%{%vDfwCCu14w>0*vk=kj$XcP zcO1PB0vS7!yg49OPm=QATzBAn{NH?l6foN`*iAk{8WyPAw!WM!^kHW_D&?k6{rrRq z|LL3mR9Np@EVp0!Zeg7VGH2A0!?U2mMI=0OH@+7&5>c2uza({YoL^GFCWk|yf#io`XLnf^l}ruIoE^~*ABgxAa<=;6&*R8E zW99nLk%GwIzAgI|^6om)C0qhGzw&&T{l&y`Nc&}FzWIjHo6uM`6epc^Ce>^I-EX&X zr5=?(Q+rFYW(vA1;pj1#hRcKxw?96)$)d^^z-Z;|KXd-;(V3b`4CvSx)~7g>5gnq% z0lZh5K#QSdn5Y3)0xJJd92wc3F5Jj|_jRj*K@pzs{Wl^xfik7dDa;?-Cro;T8X$6e zk=T<9P#O%r`oImACX(r4cu;-!%V z`;&*t@!%0<9BCrsps+!o6TR+7Byc# zH6-El&eP2Wj6KI2u&?l)lc1a(9U@&-h|1L674vNc!I!P=Ar0c?tU!>vkTbPQXXNk?m%0KsI^n1p)INTU;(Lcm z+|U9Tx++-a#G@%C2`x9I(Ld~6D-uq&EDx7h{QbUe*Xz2T@Nz%etNWtnc2K2W)@-g@ zvx$-mK=f(>lni~()FEa(6b>%}45569Xq$eqR$So<%6>&};RS zw#!zzS@z4-(-BKD#xR`@ksOFrjbRUCVtS5c2;`zZ-NyD_ZL)tIeMuYN>HE|avnCN^ z&WtzTR>da_S1^(LG%y40x6{0<>FEz(Y=(6m>j`vuNVHZ_D-l`2y`HwREOD|oBSJqE zkSGmh0MqoxQ%DDAUe8NKoR68NW-m8~QwowsNcXZ$myb82zA+kcvRg|dyG>YZJ3FP} z%uGq${HcaC#K7#&gXvl}9&M9C%Q~>D^>L6a2HbdXdeMx7p{#r zp$BZ(aVzpt_^=>^`X`jSGZ9{hydwJ$5TZ!?S50c+1(BQ&^j3 z+uvyZFvF+RS%QRt-zNJrk5^k@lP>97QJ zarZYq#^0!tUJd^V-fo@hZK=t8uc)-~eU`?p+!$Cvb9h9x@-^ zE0t9FT0BWs!SU5h*B$QBsB_r@M^kTe%wvI>&Gb`L$ZSZB0@;+Zn<^ay(mj^%=-61i zc^-VCKMX##IPCE2WuNy9B9!-O%(5#Sq$4_W`BZsJ`M4^@Ws*z2r320(_Pr(I35o%+?i>mmMQ){g$Y5e(M_V?A3e+@va^cRx^KQ?gyvi*ZSnAx zO-TC)Iu{8nX*Un~o|SqQbY1*ijmcn8?-G8@DZaLF7dLjwjg3dx8z5SUl*FL*R39M@ z+->+GlHe&2e>XYt|A(=+fU0s0!vzsRX=xDlP^3F&T_moxX?nYn9bt=VgFSbFx}|Ng%3jVIpy`(L)wyxcuA!?$!H;Z4WXcTFuQi%oSv36pr(&{mu_WR8ETI7R>~7 zpLQkL%{o;Lv3PM16fQjFLLEd+AnCR2p0~pIsq~YSA(cgr#;WtK*}C1yAJ#@0F#-!+ocbyq=!k1Hl6mV_EeU#KlWV9m+*dZ=!q8SRPe9 z7>#Rml5?*_kZ01CJ+17xkJ{^3bUuE+&k5Vdgnvk~H6df?{@^1RKYEvBK(O^QBEl*M zJI|SizTbVnJznbU`NrJ{wH*3a|0dq;640#@=BR|O+4%j&!wNsO)GgHS!Hbs~kF-`- zx!Gt%5i02~;Qh#1CF#3Q;jC-5694wA{TTw~Y1HYdqU&rlx9LGn){QWen;1nny%lhj zkkoqy-Us1?5{x`mc02 z($7fi3EO!Vh~C6hRg<&uwZBnZ!mVlKDU` zPmKFM?R5FBmfyzh7y_-tzRNqa$B%!l=)Qf^zTk!%^J&HWFK6+n&AfeHUk71+5w10Q z#3BfNTt~JD256lw0_{NpvAzPOVUbw^FosYtNo(`5KC1BKfDDt=X}Mn46|0s@uUbOL zfoqgo|A=sH@7SD687q@v&e+m`$(xv_D2T~JgkccL@kYkB)=k?v2LEC1IAUkSdJER@ zb0PEhSEmV;Llb`^E2Ybs=?5+U^bPi@7o+u3_^Mry;TwHp)xLQt!Wd_>P->*xSVW=GYo7|`1gkK=O?LP3M=nit5Lq>mGn}~k+(ca*1KPMzw z%%|4wF>MD)kGav1CVUoTT9s~m0tVe!=WD6w4i36#oc#hM6|~&^%!O5644rFPq89G` zh~7I(6pfQBcgCQ29j2w;HA&K$A1sDjqh7Jg$sUV7x|@r2{#7kR3|H*gnU&~z$~%Mf zx;EMu`rm@c%r*C^Dnegjcs>*J@_x?nRmkLDpw3sJvKRX&6wd@7jUtEkxD#qB$R*#{Jvo=`*}?u0K1qxqIOV*Kw&9sT5 zrJ%kcV2KcR=I8Q3LW(*@M4Skl$)~1fKjPtx`iG~j9j%TMG_LdjQI6Wi3oS;4x$#6T zu3K%&d%i!L2oXp1NfX+4WieUb2cCGZESWYVK5<>PXmdtaP1H~zL_a2Iqn3e_kc*?93 zj&Cl#YIa)zSMyG?Z;!FegJx`TaB4&y3oD~;K|aqmGjbsNvD6&oil-rPWx^>!WGnfl$Ff`Dt}PZ2DI z93mzD_YXVb_awDgI>}|Z-{wq4}KxDbCpPj0R4B^(_ zpZy9e4w#DiC;#FyRyK`(5z~O=;bXVx1ibIB6!>z=_=uS84PK#KYvA+F@7*Ve=iME= zq>Nd7Ya6J!EyZALUT;huAXV+7-6gN8d1pZtTc8!OS|#s5#iP58G1&%9F3}?K9%>7* z6PAU{Sqc)W{U6<-UdlkVdDIC{vec5z#nMrI5pRiQy=hLN{DhgFcVVaVFWH6P1YFAp zN6kFK{^|OJ6!otgHTAO1TIG38cHPT5?gwtqt-3DT1UhK6@Xzq1u8|T&^98=}=^!dc zTEzI>ql+y_7P$mI(~mWM5@|=QGm4Ir{NyEWOP>{kE*;A+oRO@=C9+X`S&7Y5>_!^p z5uUH*nTC_xILql89TRV`pJ&N#)>+Gbm*1p+E%hjxAhP^bc9?U4OeBI-Hb##Nrbl&{ zpQL?C`OwRQCt*&=(b)8e#q(iFXLY<%nO(w?I4hUIBBvAwyIyYB^&&ONK0fVLhj`ji zFKa&6`J5*c|M`&Tm1SaTfxnQ*GB^;W8YPsBUxQ*AgH11yhf@3FNF0aKcKK~SN`Ui| zoXaP#@2ce;7F0#X(L33qilOwBS4GrQ)*{mf>V3Hmjd1a%9jL*KI>nsib9<-#Z&cID zqW*2|VG7E90uqHN$#IsYP?A=PN`vi3Lvl_+W7fI5aRoueSedm!e7HGQ=8>_;#u8wWCQp=wq*17Zj zDh9Q=nseV9Q|khsE|6A{_uG#>Yxm!DWq9YKTGs1bXEST~i%PrZy{$=-b2v`*VD6{z zpkZHr8!Jd6V(vY4^%(uqk4PLY}|nxmbIW za`USap(J0Ern>OnSDnM*qS`XCbPtBQ8z<E;u)mtSY}~O9Fl9Pe=hPvF zQPlOK0BP6k+Sknif(IZskmfZ<_mgi>;jK(J@bkuv9)^b;?$ZxF9cFIVSn1^+T`6&Y zfs;kryN)v*9`3b*C#+yxXWFveWw>6A8E~~Xx_#BS^UCRj*4jI_q%pAG(s8=)_p}^q z|BwRa>~Po0FvFyVZPV4WT!U|Jy(eXHXBCJ0WldAtWFN<9_oPRXM)U*hb#ogtlmisY zrn^P1U-k@j^R6!kG~c+bxS=KETq@zcdvP}9AJjuyA64<&-@h>70dZ2mdUgN#G`idR z3F5@L1sA?f3brm8i7ngpHtj&^^|mz|(`muVlq074#m%!6tom0~wR=AI1by0SM+xTS za&la!1u~3+9Ir*%Si65?<@2s|4!iGXGzOUQj@J1p6ZN+|K}c&&=cMCU`Z2i8z(`aj z|7iG4m>bpk4`#F95uBa#>b_gz$748dHby~>sqN{VNoR+yxqB`%0aPxX=h9Z5hAJY< z-_dX;){m@T+pqsc4wz+hjnIy5Imy;LYP0rz+P#B(V(}lz=u=@!6%jS*_UTOaHGP+& zpgC#L8TRkvSL+06C-zkiuZJ9;@3@^>AAZ0dls`KqXI(Ty***M)pSF;#>it@u#-(9_ z!O?90q*v&jl9K_=puGSA2=H5qh3w%%M>i5G>cmu1L37mBJ8MAkw;+xZJsn?HJ0$ohl+OJM3?k7ffr+ zGBl zpU*$P<9il^2t`23ye2LqzTdr4|Isc&o9&gg?;R9~+o9+y(;Pou22^7%kg}U%oUAMC z?0x#(u40oyh{kXBeVv51uF)?(a*+{hje%D6?H>PjV2=G~9Kl~d~>#JpB$6KXHtEUV5 zadPcN^m65`K3+K6`Z}1%I7`1+?-pV#0MK zvxOf4i(H#{HUVG59U?XnQN%wpl6U@@O~=WE^0ge9#fKdIqMb~*t3`FvF^?4wTHJR19(ogZY zEK2j7w4X@wavO+U8<`md*mGI>#7u5>#Ru8=T?Dcg!Ao&*zfF3C>P{!ry8`z&#j*<9 zT_b9IY7|n-z4oLW6f}Y}cT^MY5k(Wo%L;RIet$PX{%}%r?+=@vSIMSi+>w$yW^9j5 ze5;-;=G%D2Lv8=AhmWN2+;IZx^K9B!!c38Mhuc^f0#R3N1Pj=UQ;uVn>>7z1I+@aK zDg-HIs%vik`)cChol~SsrGQJzMNyX+S(gVW_00P-6|A zA-7`lU)e;;`gqS!FZAm#E_V8>pIlPiG0`4)y?$hy^j5!$$MN`(NcfWUaUW0H#N1SHWvN_|TITvHbKGIJo-^^S@_JyismZPqN6dC3 zaP}RFz8fX~oLtAcu=>x%PM7I8stHV8U#w-GgpCt1cU4ybu5=McqK7t*UOH6>uB}ab zI?hjvD||*m8_5-%WVrM-=*@1)_)e`CVxRCmgOiM8$VeiGX1iK#*AWh__c@a~7V7YyYIx!a@i9Ie!D8_;jntyN zlV^STC%SZVja>W{&Z36^|2h3vyfE9H!`DLi|_6cGrp1&NTVl zP0e>#B+g$LZowHJ5~n!g)EUWjv0bBd+$djS_qT~KRd7=}EeQ5wr8!igzJHP_5WSmG z@4$f_C`HO)Dzs)tv`dqf(J#Xsp7GqgBeU-fMlIFCJDygXw*L-&d8z|%%U@6ifr7axny{l4IA(34MSoZa5mzqotO z`Q&C zI{umW@x}g<1C`<(vS0M&RDIhB1^Ub?=4t|bt8|az1)lG>eC+MF>-F}y7^~6q*?4ta zBti4j>f+p7Vl;ruEzhA=HOr>U1I=fD=If@D)n$0kVbfi`-kIMaH(#YoRdS0L9ahAq z(5iU?q_w(@FJ7nq$?T22@u@alYNum7%;a}gH`N{{SNwvgnk%B0a+G@a?-!J}I{5;j zt>>tGJ|@5aC?%X%j|k13tveuc9--@qGc&w>`sepwj3VvNG`zkAnLZAF&q@xH1K2n= z7}IB;Rnbp zl2f)ixm3D$f3eiiA&h!RW)f-5>+w8hJbu$XKR4K0!vDTM0?Hm|UR3M+r0tl8oIIn4 z$H>$A`jda>r_*k3foCDBeau>!ayV2wL{t&cn(-OwKi0OCT=6Mn(xhVLSdilevtPLT z>|IXy{OSKdUwwB^;>3sey{#T$z}TPlWY>2Img2}wzdtN~2$SJ<;aA(S?ofLQw;SX! z<7ZqgP1s|2{y}ZZB2lkX{aoTBr}8t~CBtWKKk38YR!Xo|X-!!aeIR})|54L!oU?z! zKby@Zw-cQ>iccYBgo81`l+)2L(N0~wE@JC(e`5Rabx+jm=vdcprTd8P6$pKw&tE#T z&9W(1A4F^S*>y*iVIcn%@lh==GeyZ%h z2L)OaO{1+QmI8*8PElqb%LSyXP@B@%apG}EQL*l;MZSo6_#TDHikV3SUyC&_B2J*b zRyTKKrGmxi4H0>{*xP5xCBr2QBVx<_wJNPaf$Iq0?>=Eem}f52e)4XZ=gH6FH+%71_VUT6j8BA_eeO*|V8F+Wdja(xn*{@S0NhU*{Lzx54;MEB@3| zS?}*xeZPw!M2g7n?~S$*ApDlN1b2{|o2RHC-?;?k{%9mc_k?T(mNd1e{qq0>NB1vM z8>iETGR z(Ha3E_$DYyRapiTjrjHkrkHZFQtAi@h~MGI2?a6_;d=i`3;aTLmeq4ZK*0KO`*SCS z5ep#469hRaaZT^+%^7Q5yosrMyGhbAC`!@p*s4hAJjqyu_?iZSjJl)IjHQ&G-w-`3 zOq@OiH|M=Xl$VK)#!sBZm-0oY4x`?QnBdJ4r8h&8@ChV8e%bvyyQ^GoJDcAPt2964 zC0rT-A@K3rvD+J|BIK35B!YX^A|SAc$KL)zNP8lE`zu%ho9g!WH=4-XyYe8+<0--#!R4jn+1bpZ2SKnx5*^9bctz5f zR&eQ_acpFK{EF%0`9gpHzHjIc5ERwaaGETS38*x(nm;)?nM9}J>*MnsUZDKz*RS~a_@+|C%gn9>^1=@viuI}l=+}_T?Cu!mIm9}< zyK`}K|9wW2M@25;9~m2acqrt2ec7?i7Ze!CA|b1!^j=UIt3-@(W+WOOjg5W}-(nvI zPgGea5OY5jxP46i{Q0R!dCKdCcYCAc8+K329lx@}gM-j`0|c>L9@k~r`}gmMy-pju zV>E`?fDqsOz3|f~HVJwmAyQu|-$HmNQJ?)EKYyb5BHr0O6q^70*VfDo7YCKxb0%WE_IzSr-& z28Y))U0PIhu*cuN z@f_jgiS!}+F67}M$@ptlR@VN-MM}6>9TNMygpjvl2x6`5e#iEBcz9vhv9H$};n~7) z8t3QdaEa1VmZnmOra*MX=;5)WIHJMnX#;h2bwk4|K|0(29;Ow#hFSy=jvBJVmO9Qf96e-l?-5WJ z4(>2}EE;IGHR*tk5}p%) z-SYC#i8WzM=OT{F&27!>!!v7!I`ISP^lZ_43C`9mX!?xtOYd90X@ z7x1vL2__1`{!&u%SL=rH_HC*S>6;F8nY6XEv^+gMEiGwQJt305eEFwS_8nW|@87>F zZ;=+A`M9~Y3=Iv#R238y92^|1tO!L#b3d-AqM~xFR=E4xiHiq1I=)R#{_yrKS(McI zV4=6ArKQ0%N!UQSX*(C!NNeMR9OeEVaP)eIhe;@yn5F|GM*n*xkc(<-YJMszD*EyT zH%cl@6@m*sh_*JN1U)V7@l>rV;vFY5GfGN;pzqW_^Yin=v1+TS4UUXZC(5>7FMD9x z8~HwX@PL(7MR)dIFuCoE7m)DhmuH--(l|Ic6cqWqPKy*0?mJZuSf zf#!k&8e!q)&!6R#lnlki$^6{DeXXfUO$mSxX<4Cb-WiL7kH74ntYL1xv@y1TLgC}% zb2pUTfvn{7=cxF296~}4Ztjv#pJe6a@&}*WJ2;ffRMcixR&u~b5)cr0;=%LDMXl)R ze{O7SXwWAoC*$9_6SB}AVZP(a%*?E(Hvys4(9qs(;hIGV2NoR@!^z3{2Oc9Mr>Cdo zWxCznO0KuYK`@0XU7larQyRa{&Q8AMJ_&KvY9*TvJb zCGg5uO)W8@KjX2Wr>6{Fo}e>1)$#U>-_3OZT0xX7wfbjG!kF+Y@_MV@e^u7~YM(X3 z1`1VjN6U;=C=;TVZkW zHgBP~Z6+uvDE8y?UrpOz9m~CIXz=~>WNx06o(`{yxec9-F~NmBZ@&u~JUZpbxVUDIo}KmKLMB4=i1r2N zy1zYXa1A9fF$4;!!eK)L1H`+zHDAA)C@~o}xNl8zL*;kKbaZeKc3;~q(XWk-rS{y`MD-%XN$4Bk9*(LP+4iR`R*IRaM)KDW|jD zp-9fjX?9tXhRA}=JG;2h(bB5WWrDaN&#mh1Rm$RZVqjvDLh0`Ac9^KNT=xngE! zruB-O>@q_GQ`*$MNRG(9jSK z4Gp{wf{BNl`;mY#b)}`-GmR&_j?)8!gGP1E z6cTIaM;j`+A{v^STs%DAmr(inC*TG{)lx|kzs`kpTbQM3 z#*>TY=jD}q?@@AOC{p7>qWPWf;sgW)K!>8nWe^n=<>LBUS^00F{XVRSiSXT^4OD?= z&z`-Pj(d}u3jN7JTRXM+3YSAq!pLZLV4xf@22{|)_2JRc;rp1xVc7DDiUu|I{NWN6 zU%zVV=rA%~5E2knyS%SzY@E70JAl9i099t(8jt*3F+2AL<C|-`! zb?kwcd|?Bpdk_J55C|BUbYF^!va+(s1wEN4C`^B1BlFhYY!7Ag4_mDdi9+MRkx-;{ zJJ}WXbgdEo5j#mI7PprfNejSB?sj1%WhMt~YR$iVM zKD}4}@{YNeteDCZ1c%eJv$V7{C|7QOe*u`igi4-~k@5L6^rL?tKhh2ts`wqO^d`}T zKr7qc-rg)5gFj9+`;86cOIB=0Ox5mO9`uWr8AtTz2tJHx|GTon!^?YfG$!xo=NG1` zp`jrs7Zn|iX5DDqno2|0`T(U)i82v#ucWl}_&u5)6Jb*K$jFG0*EZ8}0X;7-)HZ8C zrmOwABpenf5})$(+Xe^O+1VL{g#0JJ+Kz{qnS~Ov8hy3s#x*rv5cJx%pFC92(y9&* z?wR5EvxY>cbQl* z6dymotc;AavopZ^u)uo3Oa#N29;44UyGd)4(CpL)M_mgpOnW2k9p zB)xteYHDQIiF_*>QmF<+@71du)%;9?u7y@JlxWMBtg%^Ls*2yn0qi6 ziT&pF>vUcxvQ&zp;o*ulypE2Jxt0{KZQBhNHdbMTxegOHYza8=ac3vM&w+t=p!ozE zn(tUR%zj?!&n+x0qJ~gR%+bjy*E|sBq1S@ znVFd#!OP8E@bTj&v`${$6G+~B_wGH`jO<$Kw%G5C3#t;-1}Hr|Jj~5C0&A(Px_tl~ zeuYWWX~M=A;kCB5R{XpgP4L(FIMGFCwda-q0|P_Y>#twAQtiw2E}*raKn3B*5OSIo z+RVX@XxG!%kBN-b)Ykr3P(T${&d9gcTI-dZn3$N6@n=&0;X}IB|JQA zk|={VTww!w;-LlxHP|SpG6`gKG&D~c8KKiyj56uNNgppj>G3_@e3T+jlPLT0lUA&iifCc?onG<<+?h=lmf%GP!d3bS?pTcvEi5LxPi!a~^V=*Y-e65i6# z_s{k9p+=Dl`)23lC{g2>96P-U2pySV|Q^v&gXYLz1R`6+Itatl@rln z2G??!s(I|^b~-qqpbWX)9`V5XCNnuX65Ds5nvIhaed7smnDKd;J|`w!6;vzOgYmag zi>*}M&!4a9iZb#biO|s%LVEtRsf>-qupWlQo0ym|Zn^BA*Qgm9QfI0o<#P_6Aq|%h zN8#pCAFp`d18D=?6D zMWET*uAl78F)}dt`1*RXN#e&z3j$aGu1vyaYH!a(OZ&0tVvg+vRIMCAFBYSw)8%dr zR8&;Jg5u)h4_S@Ab$SDudYS?vzza)D0%BrdX3q>~7GUv@@h7Z`DalmCP?Rz_3VKgn zNU=Z;I4tT%2yK%>!HJ^YTT!mx zz+(1t099M}J2JHotvk>qFR!lha%2hxUe*CQfC8DGl!S+a!|yapzWLayH}mh>+Wh=H z5k5YbW%M8D&JzzR-dDgg)6;d0i~>D8xG!E>W<6p8ijeSF@MC^{;Qq24H#c_}c8WX| zHpMC$w5oj4kM%UL@7e*}@ z_3rEYiBu62g1^(*+uPd%R{MZciHnGc+{e27PU-@P8`uIi4vt>AiMY;;O6lj%6aZ4w z(;*J|ymwpELV;H*$hTmlAQ2GsQJ^q+iV$%~-SI1>RpJ;K9o>E-FVHKmqd+x*LnZ&5^5ULH0Dm&MQ4hs0=T zXncjgKC043U_FLlvvYJ@Sz00y_662QDDkPjKB~F@tL^ahwO=S?(JM@4l>CELR(yPX zlLwE5eccb&*kLs&`KImefRTtWIsx63X9)p{*_jLa=F`3!@qj!tE9;q*RE4)1oX_81 zmVE=ET}u@Yot2eYc;=}9Mk*IBIrF)=a7cXm{COQjAEAuxEg2JEdxS}lxITaS1f(aE z+b%ViPQ%9j^gyhNf8KFf}?pe)>L>PMM38 z)9N=AtSe|fYMR*4FuB~Fgd;(X zOO6#`&Y=f|YFrH}M~;w>tBcDX@TrMmbuCRzqg(Nw(DzFm!0_SSx_f#k!j!Xj z1Fwak$Mf>=m`UQZ2L@i#sl46a;}zD&2GNy+R5(D~xwWXzkoSc@9j`*L`D1;TzeLX$B@o(`)h*@dZ zus_h$l~r~M+`R7&BqgV$IP)3U*sM*}%0Y`)q*V|XH&RyagVdCi3{6OY98*?NVddub zxbnS~yqK9wfIU)%AtE{!76#$t;(jSBTg}D+@ITUS?gTG+`*!v`4U( z*S-Kn4jmmGFoVb5A~Mu^d)cLT&(O;Z>Whnt^bHJvfM}?`E-Nlx-U@grydRmHn|t-g zSXcLScR}*$(_c{JK+ywTpfmOn6(ggd)o+_(>!IoC>6sZNP$nQg85v_b%o^(K2md@~ z0imfG1+*EE009MS8X3LK$dJody}Y_&VrGUn8Lu+r7Z6a+70HpM1{}Ep#qiIcxh5Y; z6(=cRV?Yk&ktFOG-_LOVAi2ef*|BkPG}P3vmbq4y_^7D3n3%?(o3`$x z!jDQ>WCR4^*l*I(yg>TW(SfD>{{34~S(%JqeXgfRL0P#5gauw+9bMhE?HR%LSL-Nf zXt*dy=;$$1wRi39tx}Vd{ZC(C07gX1QMbVXuTB`ckFV+o)jCm1}^_h z1h`V`I3tC!zOexn7c@5jKW2ijyBD`2EG8x=XIldFjf}Q?(%!_zs?^u}SaA59?f)Iu z(h(w~V`4JUIG8K`gM=s!;QwF?Zr~6Z69Xkp=z1=O8?Y+~v^6y~aD|BpeqeD>S47>{ z@*ojwLSMgr4O(V8yEzayH&<6q4h~3skWm5g)mU`D0gM610hfdZ1_IL4r`P-RamL|F z(V^G^exvt4c5U}MV`AcO(y6JLhN*gc*8#2S>QenYf{acG>S0}jBTW(wwgew(yu8El z-kzMI;v`5Ofb`0Rp+W;QwXn3DFvo|>M8}9Gkbds(>KdD#z66v*naCG->3!UNE7EBU zkBn$SF&WokgjxvCjETYo#BaWnk(#Qlp+WWJ37_M%E^X4;N^h3SQfGC2a(YV2pXur1 zva*Esg*Qn_4EVox7jBfW6$*#vT7%#aV`pAvzIih=Fu>+}Xw$c#1MnC&2B2~$OArV| z0Mua5pp%a9BQ-I%xm5Mzc~VfZH}N0K?Y2ex5>%KM0CKK z%{VeZqlJ^3`u=?p(rVmMk(QJH>@I|Kzm8JAs)+w-UQtm@bL5*hZ`Rz;LI0d^l6Myn z=09uC6MqL+-F)D*NpFuP>xl`OYuu9jTJ{Gj9__qy9l)@j9!8UsqT%(!A3vmo$P$D{ zTCOWAE1R19K`FLJL{$Wp)6=siFK-~7{e1;96X6ChX21>yTN`lvn3$NZuC5>gWUt!W z*npApSj3-i6m`%@ULGw@nv;!fe)G)6)>aM(86hDVkG<{*W~n_G83nf(^Z4-q#h{%F z9u*n1Ur<8s-VFr~UBRpVc?6T>Iv%RDmzRE%w@WSfnD1(8JR!J22)xp_Hj{z|A$nua z!pfQuiDJ!qx6OnN*jX*WbRfHZ;)!`)zL%$a;A&w@fI|DtUb7jGH(y;zsidL;n*3~U z?-g+89bihQyZJ>MHMbO+h=@tIe15eMVdfrqG~XhlqS!NjLxr#hWuPrI!fCb%6)SLI zd%MDG$H>z1FS9<+L4(+?8=w^w3YC82x+&yCYDZrxYzYv7P5`mR#r+u@dx$+>sG13| zbaQi)$LTu4?K}N9Mk<+jML9XWN(U8Ev5}%;VK@4(o7Uk!QOG+97vrb5OF#GvD^?zAX^zrYJfU7tGLO<9HPjn3mwe)sqH z_V;gX`{ohY6g2AJxk{<4%X;0#99|NH-Tk>JhZyx z_G+v;_{KS?@ZK8U3p+c&A^-mUGvRqUkn4V}j5&~N&>9#3;wPJeQyF42&J`)_Fv1!y zbPsS_-`$)3du%;C-5D=~~exNv}XJqU(gtc___F~@pu!o$^YIpy~ zi`$xacby5>ka&4{jc!WdFWu4qKdjsTuM7VFbbgT-{XLr%(pcu8%joEwEX(ciJOEmF zeYUDjie=dBt0gc0B@2z7nc1+yjE0>2R%4Q-27nb0CE!;w1K3wHBb4AUhoJgWHISvq zUmdP%fb#@+AuHl#XeiWN5C|p*2FAOq0-$aEOr!!NoWq)zpHBh)mAd+imERdwRx68( zi=eI=H+trzr9DiM-@5dHU;&v={rU6BGf7xK2K@lZpdl21+|*PSM#i3ccBV~&n2z&; zgXFZdRv)~bwYAeCWW{Vg)9yDkQD9|N>Mr`aD<6BAc2hT;A0 zp{&KN5kjQ}ya+sTep=VSz`)peXL0dm`+`8wmxcxbA+ov2$@3y^Y?K>IDOeD1tuPp2 zpdfGg??@OL3X>(6vk|pUa>45FAj0aA!-x1h{*4Ix9pgWI2wd5eWNC3R%u-eE&;OhS z2oHyzGU+7?{Aso|NR*LLUs3V*uV1dybuN6QrNGAl&JGv;hVrA7bq#V4gl>z^!M$5p zpRvBauBN62k|=m!**p&a!cg%9Sfa$9T<&#|1I~tzrQhsJP?OkZ(&Tr-^7QFB5Dn02 zIIa6YPv{_hlWg?(fSe1o^rl~1KqAaM{9PI4dLv#-g zRnE@pQQ(j;jUEn30`vhL#oG{hjq`hZyONTUk0CN|9`8H-0UKs{`DhR>7Z+#Cd0YI? zTz}y+iiouE2u6g5GaI*1Qcxr+(gGZFm}^=39*&MeK^W6vQ5hB%2A~V70bud3UtfWn zNgP*W*+Zx^<^T;D3Go^v%ZuYJzuox-l_Fy97t|^8aI1@sz$*X{=SRvQ`g|%beu9kx zAQF_5ZkF^m8v-1hzMdWpH8pK@_0f@$AB#Q5TY@}}?$?Lwcp|Nfotr1BP(I}kXq}jT2HPj<0-6Vbn1bo!|2gdtF_91Ru85|Lk3H@wAH;HEz@lHh3?Y z+(i?s6iMLdSV8sz!T8Q`C#f5b1!5)JTDQfY-+CATf|tikYEn}BBW?r)du%GPE+HWy zZ^Q3SAw+|QNXW=C@gq8_uODCqvU`;pN=tWswbhvNr=|*nED#C0_CHu*K9-fHOjOlA zhH|l3s05(;O-_y$fO=I`*}`GK%L4H@OoTDg@2elpcbl+5X65GO7^#-DGuIeo-(Nk9fzO#Caj);2N%2zRMuDOWxOZr5tmxCHbdi8_uM=BG zM<9U@v9V2@Mg|{^w#>bZ>oDu+>^wi8g$06k_n6ZvHapwE#f3eod$aB>K*RmLM0fYE z6~>=6!PB<^(=X}=ugjv8xj7YwUQ=ml&62)4*i&BDm&ih7qE^3&goT9xSY2K=f>(WN;u)x!zod7U`6c@w<8h-5OswJ42SD{2ZD@O-;Q$T|eC2 zZ3{VCyncA-PC>Dttc?ZN&J7XdZWsf>>FiXtrKJUrHuh%Q9mmSb3JH-NYKe?B*pM*& zQu^tWg_F7) zUZUN(bBBRp9ZZ1Z)b#YVmoE`DH1Ir!L9>Sr=QVv>L$A@GhCalK$P)JRbmKgdIS^oa z{uLzu?bAJTci$ecWPk@iM1W+!w6+$!mG}Ac;Iv}E(NjfD%?}0PrKP3Pv`MyH&w*dP zdL^T6}N z&l|aX&{YEN{D^e4wzCrqIClrNWJd%Xg@5Zl-=MA^u5*raJcs5N9W4p$5N2g4!`$KF z5h3b(c>1SkjEV5zR|+G{Pcblj2Fdh;`e!IjIyz%BGY%))GY)T&GZh0vRQrPC^lre7 zNQQ(hsgQR$Py#!NGFuJiiIjQ=!!fIbLS#-RY_HtzySZ^5 zmqG0UhdPHrOIFqf8uO+xOi(a0G4XkQTX3e5UaR4Makw^^EePPv^COfHgsTS_w_h^& zAH#OvzG;CDt*Z+v>dy@cF=OP1Q1$Y3=wTE_ULK-B8s+on&)_S+Rivc~gBk`n>Q`6S z&CGjHDnS5}riS}1yiHI4>iYK=OiVc&&Drk&YXjFZB_#z|o_SaN)q2x~VNAD$w6ru( zG#*|Jm`+htQVJB%=yQS#z~ON4)(#92PnKXF-MyXr3qC0|YQFV`%Zz*0+y%%I=n`ci z2zeZ*ZOqKRo3P0%v_dbY3j3t_0+e+nrMR3KQYKw4u(oSzTpv@x*b>Z!tcI~Wz+ZX$ z<;KRxgSrbU?h|tITu7Gcqr*57Ue~e3GT`z8{--}d=Y>x3vTKO|A0Mv#Z*9%S;VQ=` zq)}c~bpV*zub!Tn>FI{DlkF=$QZR-Qvm$Ee*||yMI9f6k%er?}#Px;vxB%pbUV}Ts zsNPTssKSsVB*g!Og@j;K0$O;u1i$z00KBk)TY7wQa`ew1;D2QonQOfNA!gR_@MaJi zz)+uTIwL>cbcPuVI0(nq=a@B>rNO~s{T3*MF(5C(aK+JT?(wFNH;{>2V_Q%(=w=6Q zNg*d23DOg~Uk8A;-&k5RGUDQt!9W7!bz6YX?g0);%*^~)RtA<{N>0wz))X%l6%}F35#)bmrBmqr9veHm zBw2^F9YEy@(8S`Tp)!&QXyP?ShPE#dVXUaoUC5xobjw2+t0KaHA+NsAUiA1duMi6} zd!nL25Px6`MdDC_egwKzR~$(fKf8&Wo141&o9ygt7>|R528?oebOcflzkYXr|IPki z^q3A%Wk>`LgI+1Y5(o$h*)u?~IJ?9B4)MQeP}G~KU0gKN*VhN}dXB#z#EYw|yFV>B zc+Q$ZXRR=onaKSJNo+S-Trg_jGRDx}db4V`=UQ*}mSmu#iHHn=KQS{i14CeNYB1t*vDkqw z+>Eo-m0(T&{WHKOz^f-aqF}R7;6{P=0unRGx$Ts|A7%hvg@=WKNOZ9*XCl{~280E+ zqg1zIzb*6v>=}z(I*_2}TCV(#Hv!Os)M*I#8e{KnKyo){pBp)JrIYfabfn% z3YvQs?+rK{FuMjl4ws0Co{34~2{tha$?x7?aJsH${t{h~-xA4hoN;lOAZ?>RWVzfP z3ds3k!Uo6km^3y1u^=IZRDn9wBA3naJ($HXDV%A&0W)sAHRuM!DU^J0yL^1Yk5}R4 z6RkI5U`|!r=}ZJrKnI57=JVON@b*4UX3*H&TroK~G>17nz|v$;{yn#d?a`CEFLx=R z)~5)QI|Al~z=vU?(NaSQIsnfwQw2=a&fY$D>wnCUpraqSt@c&w7DLJm45${nQYO~f zCRL@4wb3YS7Aj_N1YKW(!2Y%Vt?O!Ej^D|)ARnJ%ro~UQd{$!-P>f`Zj28Muw<;ck zZ1{k5S5sYG!1pi`#81d_Xo@f;1Q4Fz{0Ge5bz8XHI&J+JF&+DdhtQ#)f3+Ee!CNLm zK!tT5meG$t<>uZjb*X3<>;5-Dqbe>Q647M@E-s8Y+zicCB>YGFg#c*;W8>oETR1VW zu)z6ZdBz1xPkw!UrNG834;J}bIjmz$b`W_pj?|noiL^a)feD^d2a8P)7Xz1 za?wCaR#vb`nzPLziD>{}!f46Cw{PD7+X>wcC)@0U>s_Hh2Hh7>E(HYzoaVQ9=m9|g zChG<>0`Ac!fk`B~`Plf@r zIrd=8NyiZr5FWpPDVp)-_h3eVDhdYk+~b2i_@oe0eZUf6+Gm{bn7jZk1EMVq{){iN zLvtG*8X^-7R9Y|!}{a;(3b#+0(rHtScZ{6kUs^2Y9PTuJB2zq zsx1)pLq}Wtl|wFjdqZB{{I@&(zkf5Kg%4ee0R#gmC*r+pGSg5EGg&VVK1D|U1QuFq z-0FF_rlvR129uL8g!p&$p-Dm?(3$-F{JJ`#3vL(=hIyyH#DuP;loYM;R;))HVbbrR zUYWMiQd3)4TTe|*d9hh(K#BX~Vds6ip;dJK!se4Gv?1{FK;XuafGPk#NCBEQhW!-_8&?rxWo5%7w%0nom=zazD4eB!YkM^1p|X&ftA1DXHBCBm`du)Bl6DH-XD> zefxfIBFR)4N~R(+l~l+OsSKe^OCqIEGDM2dTpCnlj7TX>s1!*>86r~?DKeylG@ywz zzTZ3l=Xu`$yPx;ndw=%svp&mOxrggIuk$?4(@Jc zU!Mk)9~BX?G-wl1+T%g#-kM@zKvzGwcfC8alrkK=s;H=_zfUG2vcXfg*ed5GrKh*2 zwT&hr5R(UdU@z}j(WOl^Q|$Y_s8NEC)B4D&K87NOtA;*CBjNe}bJLZbA8wxw3bOh1 zsE<>-bH+;wF6ZCw`>PGz6U_S%QBE+c{`u!o`nlaG{epw19bc$`G6NR-^yxu_V=;O0 zL&uNR`pmWvS+`?HEWbQh`TXou3(@Y~vmQNiYWe3#R)38PYts5mp1i}uVEguJ)aAvR zMr+sVgy*Byo-yr(r7BNt+&CAB7i`=4`N)cZ$FD2c9{b;r7(k8;Kk`!@2b*YEv$c-G?HFbxe>6m7F(*txDYQX|oH^gA_8opZy!X?3}YdW=RQ^le& zKAJUE#*aah@@aT!H_fx}Fh;Ewguo-f)AK-&6E?-KpZD(Bv-Z~y=5CpxO`B9nOCZ@y>m-Mb7A)Ro7OxB^JcoS8XX$q0Qskn6Ya=zk{?Z&E>ab1o=|+TY)A46I~oTH3H#_7zB~ zU0q#KI*{gt%-NiSOx3Y|VTY)Y5U}^t9=YG%o;;X{KuRs|fwoa?%9PbBSAHJq?~N4p zak^gDse7;9V`U**MzpEsa4U{NY-nH9B5sbuiqMhnt+s@|aBy%~v?%80%|a+@c>4r% zA8j|1xvzQm$FGdM?bN2B{4)k@X86&LuOZo>j|czMZT{yU^;3&iuNtaCVa#Mwmu_Eh zWa#+OqhsMi`{bBD_*6@dbt<6ghwq%eUMv6khZ4iw8dWC~_8ogZDVbix3P{nYB?_CH3YI9dl7XZGPuf0E7Yfi!fT(Tk1bS%EH`M% znKfAK;-VA1SwcCc%475|WTwE{BlCOqq;1-v2(;;N1O(kWQfv=Hcv(uZ`<~Z(51%D- z&~wzNZ~y{8^xxHIqvGQ`-Vgd$I(yK5)`kVbpRZ8|4;(mfvjlkfHh6N*WU5i~jwNyu zWIg;8kFeadsbI@0R{{@N<8<){ixw|Nuy*UyW%51nfY{ij=Sqk575;R*oah38@jms{ zmt9gmo6M)ae6(dt%hXS`O&8~T-8{QLwQUYCO7)+s)1N$kY`QPqArRtD{q2?^Xnbpu zd-d$u7VX~UXjZ@1?OV@GV-nmI%JLG=pMTfz?ftFFF%`j~!rux4=Qy>>3?AIbZpsB> zy4dZQJ3(m5;_p7b;PM&$iK9k2HdGvQohmEX)tO?t=rPG_qE-k*#QprCjyzgh-?g@` z->{~Opu+4w%zn3``M;i4R+?*TWCeNB^Q=2zid)_8NWN?NzFv{fzk9dKfAClhy1V-Q z(^RcsSlS;5Xri~|yCK)6Mt4m3&;JKAHy$}^6x9ycf5ofjX6eboN`haG9NFCL)lD$W zU0B|}f5>i?ndyO3aKmI3l|+4urGH-rrv5QZw8GFNWTs$e(|;i*02KdQd*b(kj~hbL zb)v@V=<3$7jq~wTiN67mO=E-(Mv=km3;3fGd)ejw>Y4ri{TmNWUVkfK_K7D%ReR&2 z4wpst05BbZ&0lK}fEINdI_4Jl%#g_X*bikRaGOT1h&2L^KG`U_+61d$|H}R z`=3~&S57_Ft$Pc_+M|aTpcZ?-{lSCQ?`|bzJ>zZe1Lxe+zb76N8l|SH`uYtvx>wrtwepy#EuHc>%Q zaT)5KYuEBJGs^)x->(|W({~jy(=Pc5usw0&6?O_o$2N8s22pXD3Qh$^0W82HkC!7C zD15b#fuwlZ0df5umJ?CG?q%JGvliVG3E$zAa)U?1KoMTIU`(U4sFe9>OH?8`!^l0 zl;>Dizo))_59>}4>O0S`!XJF4@9Mvyd=J?|$o zQWYZ@DGk7l#@gC>V(fQA&p9Uc*#0K*^OF-}8LQVTHUP5DeyJ!ky`Vsqf1vpOeuoap zDtZi(M<0n)3^MFC2NLbJkB?6<&jM-5IWy%Gvbx!CGz4wOy}Ww#b?Mvq%`QcPK@N|L z%X#{=lH^qEA{)rn^(l#8x7|HmXLQ?siq>PQnI}(Tz6ycnJg>XrA$RUWyipdgmno2k z08n}@fAFl+>$m#-RJbzv&?qynTcqKA-#K7UhbE>2c1Ou^h6B8#)7$hxSpE@XS zbLcd;-+PfuOM zfRs7*#`zi7B5vPEN#s&+lUdvxVsi1_vm9$kVg(+qc@(unyPBpEsME zyYq~AU5UsPHw$nM<;_n|g7ft6e}XqrGHzJYFl>{`3}t1B({((OpT-iS>_34E;cV%l zWeNYytmA@j$3>62c2b&Ygi-jO2;sZCT=%(;74(D|Y+z09n#5>dUn?=Z`!_MMa_^p#|eWbv$|E#7}n1xrYN{ zncm-zU%0Sy>()QW??^K!?by-lQp0v;rko5!Uoh!2ID$ww7@1lmSUf#e4Li4IXT&;I zG|+D5uN500E!e~>xGvEgi8M5YCVH+;1yJO)FdfbQ=f&x%=%ncHFr|E;6<2 zFey1;f%FijPd%`CvF_t)O7+j2je_tx?B6DxFM(|E^^ZpVNE)uB}xX51Zjh9d$wekc%ZbbJTN|GmKOT;Nr9j;Bun zxE5<`LuPIQmS7$!pt4;pPv;xaOlwOTek{72&ntqc&19RyH6G8J3Z^>Yr-`|ZfG)_2$6GBo}_A;J+KFvp6Z+{yqjRE3^igbk0 zXOg$1=04T{N=Z2nK(=90QX_>e7Y;WMVHEofpLJ&X?vax|zfwX{86C6rXR5eht~6H* zCValBDn3I(i-1l(C^@`eu3<8fb%lX}OXofHbM5!=h`qhNXL;n+RXdqi>KX5X~5nOc@_Z|<B`0 zN?8lJh+XyTDINhBrV!@DKO1Z)ba&|K?FXT0TxcweHf}Pt!+Zx#W{;54s=yqzOM0Zf ztE&M zrFDE}sO!*C!eo7W%+;$XQlPYO>jLDhht<&+7ukMZZ~qRmA;-&aleS6k)%OYs8PTs& z1R0LC%OkPv)e2Fqtqv?;>PX;wqIJ&Msb11{t~WIdpW>tmbext&O~N#?|7SGHqE#ZQ zx^hDBho5U9pk!(;vu#eQ8|yo}{j^W(kX&eDYo z#rEb3vAnlSW{|s%RZ=oBHtw^C!8jn5>FrFZ`*GLwefxfos1ev-2@Mta_Jwgl3+CK1 z%rulSZeqFjdht_>vk{Kv1%85hk?ue?FgsK`u!>?%=`4~_tkD- z$*~hB!ee5dR844y6tOq%Dwv&3)%D=Pr>GI#FljL=cj|+?**FK+KRhRPL~DPi{=yzJ6?a%v#Y&6c_0!hXZMWR& zmfqd0TjyVhG-uWH=KU-~er_vX>ecYfK8q;N1APk$bwByT2T`+bktTf-h`t+@WoQ<| zmCKB4*7u8!jZM0CtvYy{it7h+2(tF}E}ehd?DN59xw~%o9p{dAvPhJ~avpwue&`*n zwr;g+@4aNq71nmbvHKrvnj5ylVUCSzB+WU0@Jo65RnmGs3TS)p>bB5cZH+}H)fwjG z0g#-i9)Mh5pC+Z9In%nlxg66j3kqbU(oO4L{NvnQ9cX<5wS-{qn#zGw+?GX!%-Hh< zXo(bi)Pl;DF$HV|h&o=s$tLd_RumPHFYGSy<9*sR^GCgN8i$V`uz1TB1nn!o>VpX# zWCc=e#$3L<%eY^~B|Zw=$5a#7CnAq6@G$N@`aQadzof#i$#?Sxp^XF#DGNiPIo^!>3Z{yZp2en(2vHuIKXRqUf=H+!$GqrxNpa!3^5 z{Fm$&^E|l({=XUx9>|fVhR5Ov*};SVG=I2dS9KcQR*$h!D`JEdn!o>Mw~oApCpC z18^<%9TJI*h<+xIoKEV7&jy1zXp7wA=1p}%XUS|K=7)Z;!ZtPjN8|7b!$^^iZIB!V z!L`}cbQdzj)>gOCdV{Dw+D9DxJuENt&#f-4*>2YYm&}u!G|)ie>bkkReymQ;GfFPh z$TRBkG5X-gxFP$lm`Hn{+c+>eG;vd}2_xkW9`Y+0QDhR-b)?x6x&3pTkEK2Pyj1JE z<9ElBgYm!Ax#xajh7Uj6teq5@sB-YrqotFLZZx(0NF!-&tUjCdsn&iL)e&SK`j>s* zUPR<%Q%4kDUAxlIF#78AEh=ZF71sjsT%lEoup zm(sem{(C{pOF-?Jw!74ol?jhX&hC+kLNRTtj)Ho6b~=`&LXmOoVT(Xu`721<56f9e z;^ul@^}G7bp0doI+=Oi4R{XMsv91IvZ# z(LeQPxN+uBumr;7pbHl^v>Y7kQC*xgam<)4?^8=CSR>ko3k2m_CuD}q6H7=)=-K<7 z6E|pK5d~BVwhJ~DeeGI+(GV${(W6G0vpLZg<`O<^*`@2&rL9ZOJ$hb3{E&x)MsWLs zen_m&+AS+}f=*o%e>_q@gr=W@+?)c7Ge{{1GQX9ykr@Gw`jlN+U?HIJ>1>z7>rx8cyU;vcsI9q?`A0m z>dmJ8#6DZws|j8&UL=xW9`f8^e9g3rsC8F?s?iBc|4>?2HB`R|x5yn%qEFYZjD1R0 zM2vMew~z5qP{{I<`6|6C0RS--V0C_9s|j!vP{SRqJ)IqM2=w^wh{<^EMH^Q zgqbDxTN(VR#mxK@!PoBd;)**5mv@*9H(Iem%3qhwx^mX|tasv>I|e$aX6a+0}V#&RT9pcSiEL((uM z0nDO5lVhO@3^{N*GBUDhV4aE8^D_xlAB@hWmsK;Dezu|5N#Wax7D>(*Jp$_LY?hZI zvf5>^BQeOpPa+{XxuflOiS#5x8A-`n^zH!qb3)>;uFvXtBmJkeM+nau+%S%TDtqFR z^$8N@)ft_)xOG;qZbZzr>iYhgJ2X>~)Ix#~op5>pVIR)GFR`^yCjvX}=Y0LEt zClw}`&-15Yg88SkeH~wHQnst7UC5@fz_dMImZ+a|>~LCDBO^ItM21~eA;N`ICr=t2 z8ZpHz6OpE@@r0(mL1un(9(msyx`>FbCqV+)QdCr=!4viUlh(^GxR+m5MVd^ShYxie z)=ZeNeZtSf)~(0^fNZ!ohY1EK1f{e8>s^{{s-uyM-ak}7sW8JD75{f^ zT$Frg7iLhSLmko5&Y7r9`jZJPy27BAjQ-4I$7vg0lO8yfo zF0onYFuQ-hD>{YZA!z>L>ya>LW@W7uCMikQCGE*2r>Rq)2haS9hUIN?kMFM~y<^7D z-Pzh$^ae2DH|y!1+aJEX;69V{L&_d6jpwlCXYueo1F`KXj|g#?#zO0WJY;fUAdZgG zJr9-NKjf=shOKQZHjx2?2j45*6+K!+v^OTLd&-fKi+(kR7>v8U`_ln4Gln6MZXD-g zCk+sXuto2pcA()mvvsV;t&|iyI}EN~e%6PZJJ$L8FQw7I_@@#c5@Sv*8vV_)zxbi9t}eM>DIql8 zZvNwtZZPiB%WsYS+(UoWcicJbA#wc7nQ;TeXAE7IbtDd71O=eP2><1)A7Y^HZ=x0T z@}XCJ?H`BQJFhm3tMp^nbC{EHL289{z_O^K5Av!3)#ly&b>AXmBQC@nr|qS!5D>}F zKMpty$amVCKJ_b5r%occAl$^tfM;*V=FJh;uOn%GNrI_Vj3?r7U|?Kov*HS8yzXB)wnb*1*zz@NLh%0}#RCFZtoQ8o z_M7QWzOsRJIBRj2G-aQ=iqdlOb(28 zDtHiHnH^gSub#bmvm-}OD8j9(nz^_7*6rKthaZKD)whfE&a=G1#*U;}1G)3Mby+W7 z9GAFnmOvP9X{>6&LPG^lN`f$D#Z)=hOSV5gv=Qxw9*MB*>3$E;%gxK{;Id_X&t>0C zNT5Lrz-&S28m)zlrJLK4$V-oP4{KuXM|pG3ip8)*_ba zB=7uKtuOi&LE3uDx`ahlgZQ_?wwNlNxI&MrLLg|Zg@{he9_mrg0{Vs0un7zOREuRi zsN&ZJw|6W?eF{cTcDc7&Y`|u3Ry}YM!z-b6H8yr|Z(VKeOZI-`m8;Nt6ZFwM(|u!T zU$0S4362m#s8Sg(#o?pikHLf&Cyl_c3(kmokWBLK%j-ovmawqz!P9rI(AT%dDO>Q1 z&mc-I+PHBZ*gQ+Jjmhx&**(AJj^FRxtpz08(L?lfTbRmm1H z`J{~Ti)+5G#*Ok$Wq3KF-nIUVb~3kh_WZ;U?dd1}yP^73y5r3JJP!zigZmr%ifiaT z^#1*0&xJXTpt?Z-qEcG);i5@}@z}G%xXXokj0fNRg{B~Oa3gs@irj9v`3GF`L45R)>cr^ zfEA=bRHR4WGnB+6mw!7fb=CoIDW{3H=Xgxc1JsTpFzg=#dW3s4lOdscEvxXXo3Sv5AQ*#tmq1u6jWR^lE2TESl%#4i2rs zGo9(2gB=o887y1RZe2Bk<02xGEONgSvvW}~fB4{pZi7^;?7bVC>E~zC3yz9H7kH+6 zue!uO_2zm7p?rk%d95Txwzeti>c0wO->zEeH682_c@DYCj*{W_B#n(#)3(38c}haq zkaE%_=J^g>Z zIl$ISp2xjvc#Wg|kXcn`!{GcnV5QyDPvd$If9N-C=FRBn zaT*$_7eynmapaHyxl| zWU*H|+KSX^)bnTj1 z??vu|ZUfZfx_yGux!5JLe%V`c&raP3RE-|kZco)#i=t+7n%t1ATdApJ@YhRQx`^X; zaJhX}!_Erw9}(41*#oC^!+Rdmnl>$&ZKk2}^IG#%UFc&neK4-tLm6#1*)SdH+{aZ+ znU-vrVxt3YZjd*n(m|+I-BEm_XRlsIeSE-50kzxPox*xeN1o>Azf?O}Oj$XP;-#Ii zFKm-?{J52cg~iSdS+lHpD73&j?C)R00|Za`@$L@*$y;(jHky$hy$7pMY3}~#pN^EW z2gSwoFKA#~ckcXN_N;qXSN5gEI_K}bR~Q;@q7T@FQ||6&cb#=_4_0zmY$i50d7g5M90HA(_c-0eR^1l(7MGN(qmA0CF_8<;b4 zi)%q%o(!4^g01!t?;n32AA3+R@$hLD&D)pPrPVe*VB@Zw^a#*~Bx8Yp=B5P03l}Il z>*!onFsV2s=&8{-1;nAtmeo9zzPa@!6E#Zh#fq90=m}!ir?};QE$Wc^xmE}~+xXry zb@;VYG9)#{)PHPYwB=US;;0LTX^SKGuI{+zC!4%BGT*XMdJZJd)cQuqTg`QK`lk-7 zR@b{W%cIwtdtH8jI4muY!!OEs(#ls!6C*=UO%WpAmg;B|a1PKG=y|DOd)+e!+GDK^ zT$um&o%B%~I=~hv(&zhqvBbtc=GvP!UGnnEN`9N+5IyCDLp}CO*LWGhC7`(?LnA8s;5;4<=o2hHjQc?&y9gU&zXu!fB1pOBKif}nw&z2YqugtGh> zcS{YQrHW;*+cV-wpE_}*jiE7gDxB-*64^6Yw)0agk)v5wav}5_+iWb}bhP=$!MN#p zvJeRZe~-@g^!^QfW~xEJ1pRnFGE^RdAn$4qLFnUuy{4g$xd+{XB<4h|Es@7uV_X4Zr(Pb2253owe!FIW6T3lfu-oKZHksb!U-3o@vgLg&?IicWy@XRr|GzK z%)J&al-H@+-Fjfr-(y`D1LaJ3F@fy2w>Nb|v%g+P=C;|`Bi@bSo_nmcZ%CfK%r9gy zAM@x0!Cf>gHPfcy%~k6dLoY&if34Gs#uKXC3NF6sbHOU;_b&&iS@}%*pB8qHId?S! zi2ljS&_XfSxpT!Xz8qM=!xA{GRJp7%{y($;;;W)oOmVws`Z!gN&$rQXHR~3VJ61T7 zf`FmjFR#oOey`kGUN(};7A!p7b*ZO6Lzx|zk&$uLS76Yi`(ep~*J2nEUAFXCd1u~< zh`Cd`#!3qEsvp1fpUuWV&eSK zhh@6R&HwxS6BWA)7M|()D&~w6aOTj0w!_gQ-lM{@5bqxdc*sg%1%Q07jH|GNhv0a`T)PPx zY#RavJ=e-ros;(8uj-+M-haG9{`CLp9p+3MKBd&Gn^EU~>F{}-9?#BO$`?-Me|s3O zVr0yEcK%L!ROcj?8_%OM`>z`={MV&w4E(o`-h};P^yJApg%KPev62D&`?tR1IaRYC zU%kouLBeS}Nx|H@0TIgviXYOt7+|`cneS3QZn0lz&`7brm$zW~zi!uOn`6oukby4Sd zE?jQbO)XQ3`N^?0m&U#N*JE3@{POC~$HskFF%|>nZP_wL{?e^mhK1d`2n+@s9?0m* zX8~wAwKY-}l5WxjW0Ubvf#6UmpW{PAcNkC9IuNTI6Mi6uJoHy4S6bJz#H7 zNHlOR+UfuEx9}%WCuQ#a=-$WZv9N915Gg4eC@nYPs|PMwEB$eZ4Q9WeKjl1brKPQG zqg42e#02Jb)orN$z#TnHt45C<>uVtf0>_WmW3Gg6(-FJ4f+b_3t?iMP)?$(DS7|(`Y?PW77OR3b|9y#(8Zdy!>o3K0HQY)~wrS$Mp1h=sl+0eK(!H#KeC7`ZXr|VsxUSmb+@q z(cM5|z)VY*EUA8VMcU57AF^U;$ecTi1|C-pQTn~k;QU@O!J(07WtQiTp1pMO^~V`n z7Yzz^ECV9|8X<1&~ zqA@j@t?#O4O2@(_OHSHJR=P;eVB;pJ*gPcNF4PeS4E~b)|MPbJ@0<63|9^RM>lKBq zi^-Hv+06^>dF;wehdTRNqVBBRO~w)e7wf9`Y1LE|5IbvDt%3|dZ}X0$Npy4$NtAv6 zPI|Ir#~2B*TkGTSE83Zs+oT}+seTcm_i4k8urL)l(^q^tNPFeGk8p0(^;Y>|55W|z zu3nCd14z>Xf(9qa&R(Y$C&=seMBndmcJ{E@%`G8LK`L~~U|+gqRTTH0dW3|YYZP4C z)bi^t$-GUhlTpNrDC*5 zz|&L`XA09J>`MZGY-A+s^+3`Df9)?Y1jRr)6gJd|3tkkhGSeoGJPS-bA}+%%@jfRV2XR$*eNHF69#ggzg-6riF?ft@NS zahMYy8cK8U`HL2vH7cvF2EDfq-v_t8nqFP{59vtjM;%SCU|N`OO)BE^;nQz|7sE4d zv+W|dv=u%RP3-TVyE?u_g`k6qK7_iDCKE!ZsQjk8B4dXgo5_LC z@T~!I$*OF+=Ig7sabrFelT+*Nryw*QCN@*QfBYDLx$4ul#a$`Hc6T@r?K@UcF%AKJ z*yv#z)1A`-6>0O}@4tFYQS_c2r0hWQB0qfhmSsM98I`;6hONNl3!wlOqq;D*ilkqA zFk$T5H(KiEuJV(}`q~TX^VJ*qVF(8^+EZDJS`HiWWWpt!R%{y>oKqLI=!Whr=k{63 z%0B~5zGPpReXe=%ZRe0Zb>%&2bP@j!UuAKCyt}`jpO`!axu36Z{qKHfH0+Fa{oIV0 zLG-(V`mfJ?TzaB{_)eNggk@NR9{|bA&-Zlr{eGXyB&i65rQpgh^7D5-Cd6qL=I8$@ z)ae?%VBx}rIyx!I$;n9L{8uMU)t=3+Ks)&p=g&`)5M#1aDUC|M2^NYv9RUc%sQH2- z{5vnd*@f((b(63y4n_?=f8gw7L@APzdYLV1tcH;T#Odd16>|S!R+c8rGPE*vCUT?m z!NJ;BHl+K#rz3*t$Gut$VN17fKkw@5M3rWBW6#Ocr-2^vpfU$iMvfC`X#WCtvMY+h<d28ow|m&zH-RTlKEE&;-ayok8&`N&zeJ3Ue82uhWU;`D*3vXV((K+Ec{AzvwH z1B-=yP$=5yD>Z+9JiR&T2t$ApPc$fCk; z5BhxJFL-$2L4xYEWU#3|efuIlt>0JTIQH^RFecmY?`F`Z3!?*dD74vRL*)$qC@2mK z3u|cbBf*9gjk3PDx43XxA7rVgyc;8D^QgMvsnw zV`^z>NwR9}?(RecL`&bwjz>ex;?{&xVhsT(xjC;T!2P^d)8pJAwubE zF5P_nl(6w|tdWiwJ&0*@Pf&mU>=`y#8M+r)TB@op0|sX(0TRecC`geH0jiHNf<>cu z$Ku$Uyn88daz;jWM+YG5X6sW6T(>khvp`=?Vo8wdlQ${u(KFYAZ=y_sl0|ANgk4H9 zLXI3+h6JD4Yi3pkcZ6yQ3La2++UBPNd~WY{5iP6#12uvoz*HrpGA}kVQXQ*>l2SY% z@gUzM?wxqZV*K#vk*9=B`Qv=l+nVjKEi5e`n<~>|U)TZXT`qVAO=uh;!D~l^&y~aV zbt5?$420Ir2FsV@%MzM10BluMHu!29qY{7o*b;FSaDc;{kle2a54H#o8evlmyG@_4 zsr0mp&~qL(eV3ekIdla*CXf=ENy@o5{9uJ`1){b zC&JB7{AUvOcuBEttQ#(Vu%^>`p!S~fAhal5gH%3eu_dlqvj&ojI|Q;3!Wo#KJpv$v z`Ar}C%G1i2Xjt|&V(wO{v2TXWOhs|-)X^?N8wf{E4GKm{Fe^lLYh6eCZ@l`qn6_Y` z2Y`kk+vu^;6rogvE*rHO!k(4=z^?y1=%QbG&7Ji&!6Mxs`SF z536(NkyA-YedL9kDYBRmv+Se8!j3=L#92H0@3|df66|PQ5c(5VrJ|;053~RBgq^HP z`f@^|@p-zj1I4-#f#=wiTcR@J^r2n59+MyV9 z)1Lg&s`S1Np(o?&)g>!e)`K0QScROT?HCSP@=x|~2#_ydVxupp2)5GsY{{6Fx5xAs z>&7*LHu8wp&Y7cme%AiW@+~w}utM9gcG78xCf|pZ49J!(g-B0hk_Ha^OeD0`Fh&4G zGhn!L+e-b5`IcH28SgthOYmCrnk&;Fh2OtBux0Zd^vLoa+6xv;J=+^6NAc*h)2uIW zqzcssAYy82bLcz~aI9^%Bjb$*Ds!Bi`Z;hJh$dP(p~IU{Idm|X6`zS?cC}Hze!l^H zD=K8HcA$Nr-y!dgnK~ciJNqoaYo(aJ*qe+Fm)O`E3JDz1fVx1pS$>0`eXS>`NB3!R zaK+V%@tTIZBsc!wrR0`gQoEm-x#@vtBT3TQ45v0%krpPIhUr}a_LY3muC8**%GZcF zHN|SMlv?t1U=b3%(*7q0Oi@;r^B6FwePz$JMn-HLv* z(f!UHN4oT3{EGiEGGEhx+00fW6eq2^Zn$E_3l+8^Gz>py=ze}8n ze0^k;=Z+h2IxsNn;X^{QJ!cQFZ$CXHL6ksy7rsqoliNeHp55GFG4rJ~g1?uR-rJ-C zOD!U@&$g;Csr2Bp$B!2xAL1OI#f!y5a+Su73q4KdRY`+qQ1)-wTD%r09c;N-mdnvS zsXN7*Hq;`-^uk5G90y`m)@B)<+_uu|;J;oDy}66pvYyNjbq_Gvw?{`+(}5zrX81 zl3x(JZkkDn4&USvz@2T`vm~iA-Ul~Y~R1xeQ{6b z1Yd+=h$lVP!ytA)=dhH?GxKa=dBePjk%S?hk^i{f23SV;(dmb-V*l;RrlxL{8|~Dl zjUDTiD{WBXMvBw)=p^fcY(f|s$qr0xl?Se&Tl@F-Gv|cp+g+t29(&|jBe+m4vE}^B zQ?T~p%Ws4ooCNF_(n(%zKi&Ju;~1>G>+0U;9ZlGCP~osIB5rw`w5K{hJc}$}+X6H5 zKWN7^XtB1bJeaM;X8L)oHfJk;AUU$UL3f{k013nk*u$VGrw@ImVG==3&&v4D2F1xg z;P2b4=*tqY$qC z_6D7w?hUTF@a@4m`e@QS6Y&k&DVW>upFYjs97Jn=IGEF?Nqs78JzrnnF5~AsF-yOX zQuoCxeIz|3U~Sk}8h@1hU(V2t{>( zu);LK(4-|!pkR>~>dMmRq=4SCh zSPhLa%F4Sw+}_XVgn2qIm)^Z3Si|mb|CpX)KQW$i0ESVGbmi-WPmBZISaw4KD4swA$a`Y_;gWo6?2`Y$jXBn)k|()5sUb%_rPdvQzA zuNcTdrnjf1)Mb(k;g({YratCN3SIPYUs#o&DrRa^M_so(_5IbR*A+XLGuHVqmZ-fnu zu~2?r|A)NpeFZusOIla&##co*9AQ9Zi{~;m)zW*(7~)steo;B zJA2~fwtZ4su5)+M1CMSAqTQO1&%VF5(Y@*6719_cFI20%nx}|tRL>+`Z}S90ig_PD zKL@^(d$LqDqH~{i4*OFt{a}sv80Y%;H}8b<&|<7w^%_*Ce|M|PZ?6t#CLi^?jo4*( ze&|JP*Jj?E!)xtte}1-xjj`s#9X%SW7#X&#s}ORha~uiB(lWQq5#}I;W#TG${QPs9 zq=$~NqM|!XQueGdFbKGE#kc8okP-zcw|W4VfD?N^3xmL2^)hN-_-|3UdFTPs(|yS* zW|D6qD|-FrP2C>{Elct#D(!grB1v@j{k7QR%{Fby{8|s8(r@VWpMW2smjt)`KJ;zeJOLl@KFy4mA-iO=nW66XtS;C z-o4!a(W1jXK2PE^6yfYS!86hfEm|_lvfsbSk#GZm#5AMCO}qKs zbqjsEw`7|ooDZJ)(O2{=NGXYF4B7GP=`YOoOP2Mt!%#H~-*l3ncSJb)F4 zPL+gl#IRxHhloBdE$VNQ(f&Je1?v=~fQg8cbBVFBJBNw=nI-FANDE;2Uo@GBb03zJ zSknMkFR-1{gyJq;I)AR^-jgSTr-YSm8ZipNAd*dDy;twSWu!i4Q^>eL8nv~{y(J$E z8#+`>p5l;syPkH$FtxFBETb!Us={%^Lchva5(5D~rZqBq{j6GJ{poymf&;&4$CGR`}s8nG_v%c(yDlJ4b) z&r3-efvjhXnHik2uy}*}&Yv{OR^xqMUO>@+ zEVC*x{`~x`CS!uW8OU-yL@bAsq2R7Og!`E5B7f(H2z&C>8)~$Y2JOwuUGP#G;&+6c=j{%}8I+Ijj;HelDA=)}F#+ z#OwpB3=BeJV|O4>)7ZRX_3E{BP(P>Mu_5gut-Ni&#p6TcJuF_Tsy7Lv3~ak`ZmwjA zSK88Zg~UhIsV!0GDR#1sbbn7LHsDPbGng# z6WJrOI}pB+Q>U(+KmSwO4~>}UYo|`VdHS?tN$pw^H`)-@)Vxm!pLog%uLi1aL;w%A zT#xB>4+QgDb#-gq=4!8gGe`xv^|GSdBWX*q%)gJtcKk{w^dgf6ZDaccfG!(NmA-J*s)Ah2 z+?Pc~*7;hb$Bxw6CTYKZ>-ah##{=)^r`gn6_v|)sR*ZwG&BE2I?GRu%W8@-NiC8us zEY5iO^6|x?>_{0M?FToN51TxB=^PV-_3LMCdi+tp&qgXXQ(6}Sfv|YA1O4%s?q<1{ zofD${zm~+HAOJg}=k`Jr=Hmts_Zn;7MzNO^6r+6=4gxX^Ry;5$JIwLd3j{w8!HS(& zc~GnJT;kD!RMEu_DNPS#_uVwV{$|xmlRn%a`>5I=4WYWKW6wI|gDcG=t#&6Bp|D=Q zX1DJzO`GzJY{8f@W5V|IJjQswGJF9FjY!{T_yPM{zU^~3DTNZ3vN=;}8b>sQsCNZjMOD>R4kCmoWGfs&cQ&YfaQ zV7m_(sase?#H$4JVKHm9_4V~{A6@DYI_DwPX6wjtv~Cg2nFl5`+9^u$^4N$Av-@k& zpqC$=el(SsGI*~DSz_U>ei9Ln&MroZwr#sX_>PrH2PSVugD`TD_PkY@FJE5gL{u4N zm*#aVS3Y=l<%?*UReY?)<+HLSGX<)`<7$o^)jxyr0Glu^j02xJQnV7GX%59)7+dw? ziO-k;on))`gHldQojz+;*{ds=WU7NE8#R2d`CMAMyrn%l&k|y{Q|;j)(f8|>olATE zjKK01Wuc^>yCG50`<>OJ+4Dh&pn4F+A9;HRzRG2Q>Zd~QAL%X2Y<>F(EX(rxc99DV z+TE$lcs%I;q|EsHcPpl*f9x*^_30%h=Ixicv?Y|oNF@^$@2{DMYM{w8=-}us2AYc= zL6!V0tLA%ntQ!^7Xwk*r#d>I{leeTn zhKq|}t#s*?g(|vGw;0IJ#u8&jP|Lede+`snm>0HIGpBf0cjb!d#-K;nE4su zJg9iPVD5%eH=#S$QlXQE{(tkwyP#gkQ+kStSsr=$w2SJ{PG&yzn1X|szO4V4d&9I| zdYzc;a^Pd%ke_RBJ)rB20~(fs_>>SI7xUK80WRpT{dCE=&C6ZU@evYV62n!VbOg}-J(y> z)AFisUO5tModpYkB=8&R6mIU~GcO7&b2)T1x}c>(+es?jl1)t|&~rk*W-c5fdxEM#V9d}R^WqGqbQKDZUbxD1u{k6=#8oJ@_;xpcP`S8J z3v#LFf^`A(vf>3E;gcT^6!yvr7?UX$Fi~d6e}0zm22@Ked)ahnz~%-CzuWD}HHf&L zQ@WOm3!miw`Z2=kvGu<2ON3g9h5gOCJsv33N-XSu6u`jWbo* z{vU7oe|}xMC&)z5{*J6#A#m~duPZ#0s3>qz*tlK$#KTTGmOyU*^sOQ(ZbBW*-}|(+ z`oCVx-#ZqLqq6#c-ltH!^Y>1e7k@|LP-Vegp|f;7Tb$GWGpTSP-eP} z5NfdkR3fkT66DE%meS5E)EAv)m`rJ9+fw0x^D{9j4)L%Cd)1)MyeiUgBP}q^xz0(;LS>^Z1SIxGHZLvZ38h5Su9mHxa(5M zY}vR1!TxLPQj85&5$ycUP%Rz-{0sDBN#Wct4zSJS1)*7{>dP+Jgp6-1_VmDN|3O8aJ*;dBQ!o}78JaX<&77+@zEI$lfUlv}`$ zUt=@HnkFob-S(0~3&fUBL22@<-qm`+5%X?}P2E!5dJ99_f2leDdqK#*2|oYrC86Ws zka2h!j5~=6qkKIi42_hFs^^Ux08&?qfmCXOq{DoIThL)@sgEB`Y1YPZ&unz*m1sY3 z>WYXqbOg1Pr0r8e7ZPVYv!=Q_J?@eqTNhz6kpH}S7kQMY21Wsj71T8~c~n$b;Kxl( zeIzuHwuNQ1*6Hp2)dB%H3w) zb!A#w8f;d0FVPZO1gZ}F+n)g!5yB3C;p)m?SFpv3oji*dG%TXrr8#>-)9a+|qoN)fxXO42F(_j;{3U$<0 z+j~~H^*CD#*5Fh@-+}BSs*|av(IRN*0Og-Q;~s6g>)M74@SbVr zqen>LDH5H9Cu(}ppL&yW(r&~gfr`e$#w@rdFim;{$Y#4r1s*##Wk+!X@KA{J1zP45 zQF<}{jxFstRzk<=dgBj60D^#I5${$o^qXqY?!d>gbQEsscSp3lRLu8bkY-0%&lxDe7|72Wo|_X#I-3IPPSeJf zZBg2vKM#EK<_GycgFeU3JXfe`Xl+^!m4_XEWtK0Cf!4mnE-ZuJhU#`nkAZndH!yR? z4AA!YM@JtQ7XI?xd+OY|bp{4Jpo7R|S)2ylNR8CYv*=>7ZRrO3#{H`S2R zkXAHQ;A6m5U_DoEWA;*0fXnlCCJj1q&|!`hk!`(qH$!4nXiwb1D72zbL(zE}3%(J^2!x3&)2y}c3oWnBMu({*UL#tTq35xk62!4)W7^V z@(U-^k4Nh2RMgi+q^1TvD4n#SdK+%pgKKCnf+(aC`s|M5I-x`*Iyg8M7libwuWTlm zh%t91(-bC2fgT4BJpHlTBaOLy857XUl9G~l@6H>g?R9J7Zu9E2e1PA3H)UFY?ONo<2ogA4WQwTG5ws3zXx3_>4P z{8nMi7)6ux`+0e$)a}fFxFt|rdO`?$!Ol!`UUn0-TSE4L9n%nlgssM?(W7T5lpk(d z*5rWqX9!E~OKym@X=v`(dZqQZt{rF$Vr^A&Fzct-t`0+J1w%*sBS-ooWWgL_ksCI4 ziH!d0=R&T;x&1zV&d#2G3lV2L@;`U=>hzV7t=i$osJehKUs~EdmkT&`D#`5O;=W$> zZ>=+y>q+%D*}P?oPu$?GavH%oU+cTOIV--V%VbAQ^5*R~6QNw8JXp7&Q{Zdm@k!X6=KbrCi*=+28(sx^=w=5_xvX7fMH+s5xMQbQ&wZz5k`FNDnb+sRU`o^Qdf| zVK5xrTU0*t&6`EW#-CvQ!`eXD;mC=d5A*Yn8JSsFOxspi&AW3#I(!FwhzVPyrzh{x zS$lZ5ezLj3X={xk`k3io@a54Nz;I)hoca02^Dym6+}(~Go=^m|VSL+uysGNS!;(xJ z1TA#5+3_+`EomOIxNQZQU0p+o-*R$ub#yjo2fYg0(iY4-_mM;uR#wYOBj$I%r5BKQSq&BKt#W^G?HW-BOY z>m0%(z1rYs?&wf72OO*jj#AQ;-Nj*@s%&uXSKp3$j>r4_<%{?sH%Lkz0OzR8nc316 z|J#IiI0Sw;T^QsJib=?%2VK0l9I+X&T{zk&;BWAd;slpmY4V&gNELAf{tw>X10Ku1 z{~tc1qG4u~WMyTRh$1UVQuZd2va>TYGD<2*2o(|;*(*DhghcizGb zNR$J?_r8v}V zF)Eiqc?IFk+Q@%iEJ-=h(cx`i3QCtN@Zv>x9Sx9q{KZpk{=+a4#!6+w83ItU$h!Sj zX+py*+4$OF;CdnUY)p?%BESGH1GquzH%>mzBZ!*7*bF5TrT|N^ETzy2_xAK4Xa#-$ zjc)^~vsXn?JV4Ubmv`wf`&X<$Pe*ocy;yT=1dd`*EdsB_noE;OG*v7pck}(fqE`LaIvrp65k1=JWnynHRxSMG7Oj`08gRedBG#6Cx3|^0FkDTo;Ita! z_a(oysq0V(CitemBz}nfw%>XMM3fk7sPpNg5ZL)13U) zH-|lUaBwFJ8M{!CwkE`xZ8*+8tQ&KV-8uVf|JRML4TqbgnmRI}I=ptx7w*U3amxY$ zgb#2xz+RAmhe)w7^m%2^uKjkaoc%Zj44F9h08s}K%QKOTf(C6^wL3eDoyVqNG+^Sh z#26$$i1rF|YJtZ|TQ8?#P>gkiRbqN9E9*EYD3}o9BNn{;J4!I_OfBhI?#Jh<))wr; zST`{C{(^rEdi`T&CO0a|HF;DT1MJ+3CiB04cXo6j@e55r#o`c#t-hL`LG|@62XxS& zp?~Ot(<@T0Z>8lT=w_jdcGZzTVT@5pNlTkpnc6r!^AVOeXmPL-KM-02FAyFv^Pf(6p+xbY!(9embMeHh|xHg%OMzXZ_lqMqP0RTBhdQ2>p`ntOP z-Q8*Iyi~ZnCni^Hm}{{};Tn$AWym_emT_mVVZ4-}@*kOLKDN4_0f=Kr_o$&JAV9a@ z`*0b`N)&P43*G)^&7e4+Qg>Nh?8H;Y9v-u>FV8K?{E^v0^B9YOeu>j1t1$)8w5i%?h*Qltj)uUb)#>>)msQYJk z4DYR{-qIV5;=b|j2gWAadV6Q z!t*!(zxjjFV1$DsGms0O<6AyxMKuY8H;qNG3~neX9^}Gyea3V1!!!ZbdcWJI%0@ct zRv+Gm>K~>Mi0@9iElwhG-9eoWW>e3eKis5|rq!~4S}M=`a)`v;28sR5(35Sx59Jrz zo=yMZ53gKyvs50E^4>pjz|Z1CUCy?#?ePccaVUfM|AA=UuLs)O%A@AUoAx!_5MkR- z&UjKCvzr#p4}^8YX_+5zAe^9i$bG4t<{phoNJXA%((Ph4m7tNsXWlv*z1zF#h91d( ze@@=xej za=2o#iC3(%K1A)@DT?h(karR&PAe&B5D1$L@GF>{I6Ek8>T|{awmB&2I}-?$Z;8J) zMeWIz|CShCOYeUgL~2KFj^?Phz$ zL$X4dju2@%au7P6X6@LNy8mW?H7G~0k21e}8NVxZ782UFysa*SP0%iRG>SZQ+g*)! z_;s*T_JOG(!^^A0ONVjiS$v=6zM+BR@BrS%E_u~Oy_3)-iFT7~~_{rIB z{=GEP!tcn2RJ4YMNaf@s$2vpM`C*`}X~8v7%0RIDq|A&a?U%bh zmi89Ku~K;oYPdQlPRbJsPVzgd!NxEqCQT=!uNS%Bhoq!n?x@+rn{@RKOn^WM!l{wq z-^RqZZ%Ps_phjg^zs!zpD-}!%UYC?zY!jXf3{GDdmd(`ZEYF3%G%+|Zt^M1^M948H zk18>Z2P_lGpIb+uX!gpoe8C=~kY`#=60U@BTLg)ji|E&sphbuF@n&c!#<5N%X5bm_ zSG=v3(T)lVIc{u$Z{JBHL(L3Kb5uyM-a*&kvA*h2XppS4OIWxG=p&!loE*mX;KG1n zF70Za1N*+LhE1tw3#rLKwm_vCa<>u&kq{$1fTv$nR7wnD8beX)Rd_}slHs9>4qzl? zo9PP#plUz}3=9#8DQ#+L53CDgt4%+|%WTv~X29_SHh3>y%%M$|PgL@KH-^gx)MeB) z0AhNDs{~qHj}i*g7>+QYXF=Sa7_!sw>N88>WK@cfW?GGUdU)VLa~RcOxBoi3AJL(i z8fh>JO3~t@+II}2F%tRrew)^a05SSrIDyT7@3v`Ak3`3R=XN>KnHhasA8^&9N0){t zUkKt-23-XA0ifVES3yGI#KmDYkgK`5Q&9ILo$?(U@48Kmywp()d~+Ld=Iz_J(#wJ6fFBt*B@KuYH7@Uv1b%8l-si#Ym-Z%RL#Ra_%@NOp;DtQ^Qm< z?%m@;;elLmzQ`v}CZrJ8{itXs#fRD}0-}&@pmQS%4i8-%MK!5%cJE^;kNZtyHt<_H zP0qftr2441*v&E50o@vCZ?PrlrVvdFyT(elsVCSB8|VY?&CSiB-puL#4wRw7J7zYV z-dYtS22;}kz&yjl@sp(teg zU>?jd&@I%BFo8C|OMdrGdGeWGzlLT!VX#QSDm}C^WAV3M|Zc!0sciqOJD$~Wo0+Av%?HB&nYYW%nSv=jR=q{+=)UNLqJh*b#zQ? z>@+?D`XmxX_CjMF80hPuL)+%|a;>hG2@E34caTZC#ez%j+Kt|L?-x{+F&uuCa?IWW zpH*m>cD|Jpe0bD31uZwwX~!OsHk+FP27Yk|Pd_W9OlH^0mLVS(6C;Uvn)psKeO*~n2H|}V}smiQQKZfIdaj zZxbyoCGs?r<2HrPbF0ZfzW$P?f_^gS|5# zSXe|vgTh)YIAbd4-yY5~3t1aGhq3Oa>C??a`XQ{u7>=3RC2*^;S~Kl&L|Z22z32dr zrg7(%=6e&v;|Y+Nz`O`!L0s1OfJPr4k`q49862!L6Ohl!cIXf>Msj=5(Eb$<=+F_h za>&U)SYb+$Yx#?yCE6z^3t`{fqv!w z{a?6I@$a`<+pS$82AOxmtOU8h=qyB?K`ezEHTdegSXnoKV3(ImS$ksa1=_j1yu5)y zPp`?MNX+fnIBb<#d}%(gx!iIcc0Mw`fXYBVw6WhZo`hx*M7RA%k0uYj#bURURr+c0 zFp_9!{bds#e@o~X|1v&+E?8PR6Th$A1*Ar$`$BJhbxCID);G1!i5L$kYBi<@$q^N7 zUUwfPIPTdcb!-$WXU^Ebx)@9CRthEs@|a^{dYL*h$l1l2LyH7{Z2U1-WoT_+SPgx) z7T?)(=YIAUH(~6X)<@2cH-h;t$e!vM?O4ySfS44FM?>ljga%~=r0pY4XTe-M4hHfu zy`!Z`+<`9)i?0H1h5lpXT;@zs3wANgm~n@1%hVz(RG1oKJAQn>j7&dF14_3ZJ(>;a zDc%%O%pk7>NdovC7$(TeN2yT&XI-6ba)!sA!+5h?vIZ=1(m@izVy&mI4-zD?pM|F( z!YKj7YOR1$W?!{-ge@UoJ`pryxck;%i>O>*wjplF4<+5sV`pl5ENNl;AzV2br*IoP z>KZKQ7D;Z*T!TvK+9S5V^QFsxp7fzGDz-6OPK_l0u*We zha{z>kYR_YuNY`L0;w%5#&C&%%kRCh<`#Q`>hNWi4Zu-p_UOZ_C@G&TEEm2)l1Q~F z^=srCfG-s0c(t<4XS=$*YKGO-nnC(=Han0!kE0!|3SZ%Z*H=q*rBS z$6&l|*~7!ljS2>n%nXq4W3%8ENW=L1B~&+nr?7_t5p|swk(*lBhiwJ3jBkpG)`iEU z*Qazc%hLoB78eWha&tXEYoEVYXU?=cM=a?YT-`u)h*4R%8&p5yBrRhNfD*94hq^j- zKJB(S+}3bL{Vup)L69Nz#?GZYj6Gm+r(FcN4N+kj0TG3Q4zr_~aF4R>D-ViA*tj5V z2<|9tKCu8fEk1houRvoV2AA+&%u(W8h8`F9XcaPQ+Sl>Z&ZDcV-@6az>Jbrzd+oS6rIYwKKpoCHgEwo$BM2rJO8s&{W*oT*vV#SQJJ=(Mr#>uP*i*r7oX*lmYdu%z4iWn0@1rU#K?T;{vs526{A zftW9>EG`1Vxlhf(5DL^ed7E1^U&Y>io40P=9`qFUxmrh~Ys?%a9=(10_SPVU;+B)ou%8B-`D-y}mCRQ$*-|zxBSL<(_Tz4A9`e0O>k2 zOG_T;6WWLgTdmK1kudY4EBD0NW&6)viX7pbJUp6I{pAN>VLm!K=K$b; z4ZC#gJ1jXofd~_eVtSF@4)vJ3bre2CCC>Wy?-$2)jp9LCM?BYHOR@}34+M_>n~s;N zoIA%rz8OXXXc47kc;ZyD0g`pUUoRk$;cv1~n|v(s_Aq3iErEf!J^w5W3XtXW3aHOa&cXAQgydtxzMwT_Z~lP7>Zdazv(3AVOe>yBUBi9Sh49_;-*W5*0PdGbd(o!=6-u z;}aO<*uXNodyx=}sRKOWGA8ac9$^L-AUT(Ha2*Ej84^@2U z`HFh@?->_gvBTs=?4kjDV;u&sgOj_myv!F_4?jpD!=evF<#r2P@i0LsD=C56UM=Vv zN0;rw?LDpN5%Tl%DJUlBajJ_!AduNu)yy3MHvtlH)qOl7;2)e`qiLA>=8SN!B(LFg@@fR_nPYo)>C8;FpgA>5h<&Aus z&X2b=v7uw0Z{T#(?>7hqsKCn?SX+xZ^4Xx>${B~mk@)7fXBA) z?V`L$#et6u?uNw558K$W;1g;46<%Rb{$h$2T64^SO+Hy;%a%3SOUbYoyl_Ds6){3Cs-Y#bWpcG+g81s4>@Uxq+G(mN1f^h`1ntRFKB6KdTzePa}S zx5>vUpWERJ&p6ei$gb%N85Gv!j?L49J1=c7I^Vwoe?$Z!8x1)-epD&&KRi9Ewf}i~ zxAe`aCIz%)IN+CF!m<@8^ly&`uO#Ff_(S;^w{4U@ihnoM`RpUTQM6 zQ+fhX{&Iy=#cm(29}AfMkA2JFXR78>2o-UGpj0qsB$>j8mI*ywSk@Uors{Vjgl1|o zg6s*{bWu3Mbnoq^fB8UyD=HsJbQM-sX<0k`f6Frab-+|%=mx6~I8x27?9(hn?D8+sD|MKKzycGj~~cQ!nPn`k~Tr{;4#0R+A)lU*UR>{sUMiGdNoe4W(})F zbRWfz9h0Au$iixqxUD(Z@D%6@bPKB`*(hcCF(pK1YJ3ybjvX1I{21^@&?M;Qk zbD>2@e?-s3WKdivdzPJ2)-Zjw>*8U#c>FPxkAm7=isi_zxjjRKg47m5Vre5;^RAnu z%|d54n_4DpP?IGXrEh@?Wx$Kz z^TNYx`2p4eN9_BIpi!G;0TkG12(_*Vq$2@*Ys5r}&07~h%hXDqU}sP4_nx&o>uD;o z2V?IvE$hV6>%QU3k1A-FnK42Ok7(j0$c)KUm153eQ3?1xtQt8r@43G z!dz_CiWEYmyRJc$nwb@2km)l$Aa~_T7D7jHsY4pat{@bBN>LFGWQ?IFu~J4f(EIzB zLw=;GTH^KliqEk?1=!WmbpyGlOMq1p`_$Kn*zdKf#&nO z=!9cG!apA2xB4+(v#VHcDwr`cTKM{I+&ZI*?om}WyCC6KetkXidp}}7q_^PW6q#i<{eTx6I$xYWx_AmZRgs{;OS70tOnI8N8|Z<13QKkKX|lx40-FA)#fvpEd+? zIbMcPNK|{$G*30op}SmKEI?z}j>ueag2c?<-}@HzR2SE$LZ;3c&ifbM1ZQIWD(xd1 z8ILO9EgA}l5Kkv-06#+g-2~k?sveEBIoOS$I#o{2(?5I)qm+tSm@t5fk*SpqRV@67 z(Tu)D`w}Z|e~uh%f!7$X#)&$b=2Ji$+LLcFfW!(2I922OFwk#mY)l?91-9wCMi2Cu zH(V({PXyjJ;23M9H$mivIu^ue)J5`%IcTDR^&w{k2(@5n&FxUH@8HoPp9k{bbPI;i z$cGPIrU%$_^&kZYO9~MMqaW8|V#|Q=f(eC*DBPbfSyX**PJGjnJh!F}IvRFy$j}h5 zL`)rX{U&PDp(1}%Bkgmkt04dlNSKt9SAP`uL#TQT*6EEl>b!hY=hH+Vd7xxNOeq&_ z2=uRU;#+_VBFCo^)7ajO5nN5^0kB!)iwHrFjKsypVzd!x@FtKN-5=gWM~4ntg6{5a z=!!6GKO`o`6=+oHtG#CYS1gX#~Jhp4D+%`JKWNH*OqHfZ7D1d@}VPo$(roVFjb>&p3rHXL;} z_)s-wYs-^EfOcVutdgvOEK=X*1i4_f1!UkSwmk-OXWDYrswl77or?9 z$gK3;s0LR7WZ@nxGghtda7mzt6NVGh=?HGEij0q7A=;88JxQDB3 zB60S`uGl7dFm89$*@5@4cHu~HUc4cH z47>-1E7oNM$BXlhlO=0#g+I8%Dl-Tv0VXI3PcYyQ29|OelzwK)=jV4)kT2|O;XQj|fN^sLs?ofg)Ep7bxH+aV-ftVd0 zPIKdpRd>r70R}i(sO6&tMj?bkc{BtcIw-1GG?4aiHdaDHQWAZO)hN^M-9&dboI_^H zTDaVxxwkE<27;cYuZ9A3y(}S$Bb=9={YfkrbvaGQTflQ~2BAH~goQ?4i?5wX-JsYz z`s0V4Injj*3Fj7ec56d5GO=86aWYI%*P5-d;Ev_p4(5DA@1V7jck`8r#p~; z!}Y>xW{9iN8MXyduHUpUtOMcmXJod_!DFd5Xs58s15huz@&lQk=||@f3xNHi6ZFcn zXJ8pXjM=I1(w)6zs1ENefF-qI@up^G@@GL2hVYV?uBWRDP>Tn|9-z$-ZM$)$#EID~ z+`W%Bx6QgRCZ^=FGQu=*pe)k2fzg904rcU}yC?QqsH9!yzTis2iip-!>yhD1a|l#~ z{+lEr_k4X_0|Pdiy(>y=?#4)w(^tb}4gOb(Ve1G@%EE;Otvq)iqS|0rgM|R^(DE1~ z`FWzAp*1lqYzl1-9|PPF4mUbRfQ80`Mf+QuskM?SIZbE{B4Ke%vF(eLFeHG(C0$5h z;5`g^Fb6>ZQ!LX+T6;QJ++HibzXg!rqWz)KbCY8NDd2;uYm&ckQtmLtSXcho#vVy| zA29pFML;88>aq=5USHt5z#qVh!QpYAd!%MebnKd$ zpD!%5Qz54bQNVJA86%7~hTs!WV)PSRLxz_b9MRwefV_mmKtjNnhpU^*;&gblaDM+x zkJzbsu$chYA)O0PdC;W zlEGI%@$Txv`|u=4Q(`3?V1HLo@Zsafl&$Xqsbf^Gf@Fess5xcq<>i(9tKZ(h?KcA? z+@P(+E<~a`n07yo0XDXPH6Uql@j&j>ud76~ZK4GZZFg}k2HwNE10;K}ucEB&_j5H~ za}oYvx;H?dk~P$+;2{$wpzk#Fb_Uywx~AyfAKIQvzb<){6r72Mu!x$c7z_itk3=p2 zK3QS3$5VaA-)u_YOJ*iQON>f^MLxbh;uVOT>$MetQdrN-Evhi|o0E+CMF|=oFqz>Z%no~*Hlkr4{XczTl*jWiTn)FBE^CFO|Nz=gJD zOHe<;7z)|a<~9V!0SC0@zcrfyc^riHcI!fHMu!h4eX|gWfq`x*>OgaI&cZ%yT24;e z(+)u*1v4=qEr{FG%uwfx)REABpgvK4z+aYn*RCW8!oWUhzTXaED(GvwN&)L{HfQkU4_ljr1 zlBCW&uL?4ISo7VzJ6GxEFPq~yta$O_Pjn4i{)1vJ4-^#3WixO-%{Y-GUrV zhyhhm5LRz=RBi92Fhr>Gjk|ZRc|+)Z(#2EDFRx)Y!3~88T0}&?ax!sXiG?BUqCY0E zG$DbQrDIXcFDgQ^aI|s*0AbzPT?IsA3&;R!LZh*}I7ui%=7tg&g%Nw8#IYkVxPgG% zu+sZzdtyvXH+G#O+ipHOScVbPF2G)Xy6ouUvXhds52rAc@$f$8*!Ls9K)kkUz0Uvm zxO&yWw?paKKau57ldjDVfJLT}wdLtgQ)5GMo#;Aq%+rX0FU7yB_EuOIF5}_>3eL@aBo8k)&SEhGntv0ZNleQ-w^Ybyi;54+pLj7?E(w!8*gUj=Gt!iw_DGn zSkBKkaVmKSI}ky`mWFFMttiC+AO}I(HV!4;rmB;^{smM%YiRVA$d3_mO_D2?|Kb71)3e^PfXt-g!mEQhfGhWnLf) zi1N#T2@SVrP)dx1#6?BF!)O$9m8SR&h?yt9eofWi#w4GJH$#?fvIFgYPr*#5`A^hq zXiR=Bjm7KETv;S<>lHz-Q*Tnc9HR-;1KYf!pVz zbh6V zS6$fj-=4aEc&6rKzySAZbnXBIQ<{@=aMkE!h2OuOgZ~rY^fE%k()f=FJdmyNzmn1Z z;U)ehMkPB*oZs_|pq?QZ08a_(*H2~q-(WEV9%^zIW0E#<;6{^ZQgC$z-UHbQBrzhs zCU^`hP^XFSS#m5dU>`}+$~6(8CsBO}?|mj(xHzw`pJ4XW(fV*59##ccz8FN1qZw;; zbyX94yQ!^GSzq?-GulLS6R^@b!73DIYEZ_@VmSFFaY!U!_ys788t^8n25r;{RxQi@ET>7a#uUGMIQ7kwUu<0&F22= zZ&Zt}>%bQ+idqCZ1~8LocMZv^0%;w` zx6fi;4w^q&)$!7MJ@BbYnWVL59g<}QrElv(Ve6~urPZx_$RzUqiwm0Kj&d@euyCg< z{XFeQEGOzUCg;%Q|nA4Y^8VMmU%+1u)v?ip7D3hk!zKuQb;D^S>?>~NE1%rQ* z-sBGaJoZ;A3|wEyo{yve*oDm^_#J+-ZBuy*!{{2#$&Yv)!fyCmPd9nQ&RaG3xZ+H!-+a##H z$9Z|CVU;OdTK#4`p%0wMn|H3gA3Fv5+3Xun3D3Fru)G54R3`8KljEewvy~+WMF69* zhUjcKrd-47jxn7~-qo_uk%3k<;!{u$%riiYz4qlOY}wJc%k_( zcl4!1&soJy`!6Y2gY zKKih_`3RchW9c=ohN$yZ>&yJ?ob2p_$g~E#!yaQ0Y)H7wP96U}+eAjT+2wqJWq3+4 zz!M01a3v$}Aj@}S({Zn@ALGQsVu863x&0&c+9gKtw+QHCk-$h48oU@NV-@dw2*v;% z7>EEvD7DklFQSV73N{@SP2eyf{(K6XvCMQ;qMR&qn-`FfH-jLN41th|qc<+=ucNXO35z~Oty)p`~V{n!wEnNwP6dLhaptl-2X|ohFFffc8GJDGYz-Vt;1eFO4k@P;=|sW+=*VE1)ml z#;%;K!5x9tM}82VZ%7Zr@ECJIXWQ>d=mKv=Lev}_Bxnb&3j(fVB4(}eCkU%S$2!jb zwz*l2FOn$RJF=iY_HmPT5K9e#e1oHjsTGYpk+zD{!iokGoF%Ng zz+0p|=WgD)L&0tiRStdCEj0AN^+udLFoy+=6eR<62KzDRN-Y5h=Im-$={m{qtpw6v zyd=Z!_d6&lLHB{3@X3>y0mJNpAHJfyar3~x81;Ah0>$MK9Ja@Y$1Yq*Yf2 zY<=C)akZyl0(&q&KT@rqb<_LFyb937ZEH0@(LSvEpE+iUN$yA>erX^VUB-*Aymv6&Ey04wnb>pcljeNMEX)a%*V$- zreM{vMlCLN{CFDao(H%?9zMK*>Acz@XzWIYhMcgxc6FV?^&V@yHxe!>>sSoY)>x&` zG42ra_44#wUS3Ap@&$6WUAy;*iUJH+Oyrry ztkN&O2U9qzRblHykZ9T`F=8nAaP3CH#ajE=Ce;Y;vxto=!3vLdD81bp*b&&J-fQz! z@uk~#y@Lm|DMlyU+@C4;^}t;M=Czm6jsqFe&1eUBilz6(i`xw&3m`b*)5I-)`QUEw z01=S~2!^gb1Zp)&VP*~I5~Cw=#*Qs0o8VR^Y_hJ&9g*L?_2*AP_`4BvudSM~*N~EN z;JATF6%Fme$Y_)c&UI70#lJyn0XSTon+xFG7mYQXjANrWT2*Cwy1R|h9v@rI0x2Rl zcLtp0rTlo3>`tbMKJ?NAHfhM>{n4t7o=G1Zko z(}A+d5Q^z$q$<`CU$2hd2DsV-57jyoB+nkMl@(*HhOT-h1U~>1`eY4l-K(yt8!5P) zH|9f@lj_4{6v^h}K!lG>8ZQ>!WhxS*{WIn{71 zb}bR6AvGrg#3mMggwRh~P5NDV^MC58 zTJOzi!M_2cdTXp<6Q!FoWtqqKqe-%1NchQ4IY*bJ-|S5Bi}TW3e`%QScj+}-P|VYE z|98(!cTS@N-|kE1m=NaBNHV^8NOo!<>RST;P0}@d#%SR9yTJ1Jca(rX^^^aSG|=bl zKZ_Ls?vGy{kPu4epGbgteECb;Cc-gYJ7!E0+L?Qk>iG4q^i_xc&{wG|XBGalzKUcEL|kY8&{qk~ha3J! zeU+P@rYSOpdY+J%zx&LL2F%Ic`)o+q)rZp3-2Z%gYHY@1r`sd1`*EMAj*khQKmX(wf$(Z% zrX&l8(Y56(lI^J3so?P^$ASlF!teFg#N(8{yG>P+8Stg2R4fJ>E599s5>zNDVSfOi z<|GGAu~;rYYKZ!}xZ zC7BZ|CN;d&TbvAIGw5$F{QdEUKQ@ZD?L+CPt)@2p)^yk=yP^Z5k$~!ZPo7-4ynvkn z*{UcRCS8`FpHoAUiM_r34s8a)EA^wYEKr%)&5a*|fE`=99%#xC0>fdqr%+~Wbd**d z{O>basVuBp=sySlzZ@0)iyz3vs_m2&To% zY*e5Vo;!CH$dqCdx+{1;i8I|{x)Xb6et!P(QRiT*A#6!hdqkeWoLn_(7tN<~;@oX3 z=)P^9Vq^pdHp+WsQY;hmo)zcqMVUOLD$hON&yh4!wJl0`10M8}3g!pLVL+{xtV}Px z5kJfUC$7gLmqfhQFo=XT#dC#0&kAV_VYg0^5MF)68>Rk)*#yPb3WH1`0fBP#P&5GI zn?;ZC@RXry$B(a##?gZmx1S@%_9KvopAd;4XDZj~w&DrYi?EJKXtzE?$3jPEp7DDV z!L$nR5iTB!XkM^C9I;gM?Rc?>su*Lo3aE+1tlJKr1tkg0R+w|p->Oh});WoZ08}_O zv9ixww*|dMZ-yOFn*{kv__r-zuObWWo9lCf;w z3x~oxEt3=|;S0JYNpO4%Psjl4W@*%HprlujVhKB?fDie3D|7&vCvGgDq7ssJgrFzR zo{|S`7y~`)6{KB#d&+3y{i(Ot3*3LO5BGv?S#9b*Rq-2^Kl@MzH@Iykn8tr!TmH(Y zy$X00;HW{dPwv~)p$n#>OkgyiNd^U(h?`!&{u;xG^!ANM5isCAz!HYQYWQ}+Evv?C z1{xpi`ACRrj^wW@{1S&VdJ#tq02_4Ux#?+m>ZZoj;WWYZjTFp;h={m|h?SNz!pGgG zVMkzSYYXeEz4qlqDIb9ROr0{Pkz$OJDq*c~`LYf;i$|`kHgel+fren`r2%NI7T`emS_IT~<&=1^Z8Y?Fx@+ycV(_ zHhMx0eJi0@#E#tihsEVRDS=@6w+B<&q}2FYAJmKP5ULy6Qp(og6TDD$OPy`|*CJ6} z{$`=9Q?TMlkxv{bWw?r_{l-nmw*?Qa_EqfU;9EDkaVy8x3hPeK3ID6-EAkkTJitgp zb0IA#D%|*U_VqW=&vqr=-Ir8sccy4Y{>Rg67{=^p2rbFlLhv&N+5VA0r_e810fxAe zc-Zru8RVmK@PT9EpBLL-SP^&s;|l!0JSZ3Re#EJ-3bW^!0~aq|^hnNlUp{KT1;A0m zar+s!-_J1qb83Ro#)l7Q6zF3ZSzWhX9_8{{OKoi0p<~AQTin&f3?=+7i)t7FBjT>1FCpgy zF8PSD!GtP!EZ!uZ>Q`*UHJhBcxbGw$Iv%9w30LBCddEWWd;Dsij2_zo%~rwyv$7p4 zZ={v-rPwXRm3rO`f=&1*~*$^4ka5$jh^wy3NwKbaMA< zsUSIg)&zi33N#9;wLkc6xBeg{4BQ6^vd$br2%sE;nmkxRw#V)$^2dhba9V)j`Lkya zjufFFkM~_K5u6;Z$9sQuC|9rC^3zkE8(6=I&{+46p2K@z>~j&(3cTYh zEnGhRdWUei19-bbq{Jp6eQQSwg@hdo#zI|PdN#r+Ot+Y{{;KZDb^dN}=E@{nxROeC z?h(6JRp@ZJk!K*));X0t`XM_gX>i3Xw@caPM$VR8%qGo-n{tqd02BnxhO&~< zK??ISjER`}+tMARN*;A_9hOkIP~6p1z1>NIHsl$oT3lD@^dxERq-{>HXwel(EYx`( zS2xwRuUtw|;J}1kP@1~Tq{v^2SLSUm0fVoKTo!w`d0!+PLtVK z&eccTR!Z0hGPirEvD?OP*}HEguyEKsM?RCn!*o-ZMXu_&$WYWL%K}?}y~GY3YnDG< zH2m-^yEgaf6n6A%CH8TVJZC-0BMwL&1VfCL^p4_1babF^2$TtgC|qxSr<^m8((c+z zCZBkamsf&~E#dk|0)bckPW!2r^$aDev{Ci_ zPnWbD#jo&=eOLm5$cCY)9L$|_I1ce+Lz=xL?sX3^EFK9Q;^m|W1Z zw>7rNepjn32jXVcDN7 zA*v(t&n~>l-Wrgoo2`4ssKh`~Pm&%(0e~g07>A=k0qzHU1RxR*baYbXx!`SE<$;cX zVhEfZYIWoX%d#!H-Ja<$A_=2d1^7MLp79(1+ZB#Tc#J)|H*fUZUO!RGgVnvgWroGkVCxiMI)KXoV)JJJk1q-D zv8m-a=6XxoYvECLw!N+GchpBHC+{H#1{cpzD>T3EUeTWm-JG5GqV#J?Hi7sTlwm6A zhe`?xJX{_m=e$Y`gGvJ0odJuvBXHe z87LXPu;gO+iYQ6Ue3%anfysE6zxD?4(*-x^r8`liLiC3*1Zx8&H`v6`%I7bNf>eW{ zb*@Gl=TlhD$5P5|OuwOOh~g47j(zy>B}UFP>cAmjsoMif%2osJr@*6u3`FHhzcjop z_ELFe;EP-T5-MDL*HlMl95i;3QP;QH)_|8%@w@LK*O{ocv|aLVF{9kU=u?EE9QVw( z_5fsTNtnIu9pT?&^PGd}h^>+WK3*)D3NAl-Fi*(IfhKo-P|t!T;XI}Dh=ofe68_#5?kQaxGq8A z@k-E4Q7M7e+#nOlD(MD=-)5u23NO-##5`!)cz%lgz{HMLdt#;(NSO+6hi8EB0G+|w zggx!?lPBuQx2hDAN_;o^aNsD*<~`SNGZBp_siYT8QQl*4g{%=()i8YiH0oFiV8!El z%ov1m{cizDBdgqSdkI@Gp{cw4*pbY@F zt?6xwI3i2S$4-HpF6!Ex5{ib^*Nrl53p|Qbdp7&5p&jm-A)9m<8WYjar?`DU4})`J zy4Dt?pF%5y5Mv9A8^O0!g7E3Re*H@H8Kx8|ZHKbmcU66pcWG_Hw8#;W6zfh8q}7i+k1&DWIp<{ZN==Pu;|haYrQq&Fr7 z@(kV2-QB`YB|w#_dsrphbmgRTZ?WIO99k)!+Uw0G=BGPsDqwG94Nu18JJ)^`c6M!J zQ7r`92`4=A$Psi-VCEI%Vih~+>`R`g<_=nQ%R5?i49YNxh-b!G&iZ8874q%Kn~uAJ z(Rzy!65!)3J5r3?H^=#nx?UJ9A z_#!zh`N=&@FQmi?8(eK224`|q=f!AK)9l=w=wW+A499qEA@Ps#2>o(zV1kI$YH7`V zoyIzHeD+9`&g=F)lb5g3fex{>@;!Fr(PUfNMO^trj`VB?y5fS9{oscrYsAEK`aFy! ze;=^j>6a90z517Ij<5`$|I@Fjo0JQje4s{Tf-%Cj!%b5}EAQIJX z$B!J*0i?)D3+KFV8pVgQq6}9F!Aali>di&Sl!I;zFH&Eo|xv`-5k_`$iYJ!Co6K3Arg&i9KI8b&=mskYJp8zI7#Vo#TIsQz9zD zK`j3dCXf1S#H47Y=sTQd5JrcCjsN5dv6%kw$excjNdM|TJp6A8@4uDC_}RZyvf^^(3!S^1n|;L7HPx=xn@$m%iif z;|S$s_qS;o)iZyqrT=!ge=4y5!-N0+jeVXn;M|`7{l36?HUdGMPJneD;%+P*OeLZYj_9f;pnywqj%2f)FyLC}HO5!A;DuuP)dcMJ^4 zSFinU78o?5H0=hffp+#B>S|F6KM&ZiAN4TA`2srjwN!}kc|e2;q{u!D6j7o@?|u>& z#~)D#1tvo4FxsbKVk&j98iHa1;2lh#=NrZO4T`V8OlGCVLLWo@cXz7jS+!LZaE8xzKh-QbbsLdtX`l3*Xd?Mn^s?8BEV?7jX0&gU zsI!f2>G7EVUM?%tONKXOlxv0*amA29P&6jt5OPw1xM9!K#zXH}Rb>K_I#2uu^qoXh z3j$gWw=3>XU)Jqqq0eY6C>H+*7LFM31{TJ=ep zH9gtzl6{S`bIDr{<=D+sd;su*_%%1j#*y@CvCnJDcX6-)dvfjpv}v+zrrBS<@HfUB z9;^HH`1_*&CEZ&!x`(za_&;K_9Ba>?B{?enWVN3E3y35p-tRF|#+GKYJUdlY{QXOu zvI3uWr9tLt6{GrK;LD(0BBJK{P1u6MOSuGRFjL1F*`#O&@+!GGz@spPSCw7g4uao_ zVF@abFO@iZiUhkwpP@=fsqo=o!k7~5(2(&6RcR}X@PWNf^cTsyyGziZHIa@d<|5(9 z5y6lr7qAtb2KZb?IRp6a{)# z7?8qMMpt~80;&=7N|c~@&VS2g2ZMgkp5LfBm*-c;cAtR!9(5!)-F9%jRegPBt`bT# zl9d$-KuG41e6o{Zdi?6OYa;PY#00TWDvy~O#xp7xOJhYPBvRj|f@orpePzHLz%ytZ z$ZNQk#ZU5TPuWLOLR^|r34qh9SvH$qah1KSIzS*eQ2sIQ`0HvnP|ByffN+7Inx~-4 zjoq_j;K4nzfzqc*N#L&h{;CL}B@sncpxgH)B-C@01E~ig%Vx6UE6@IFx~+#T-kmyi z%2`*EV0ePX?+ic9wwgC=(mo8Gg`_zLjC=`MSMZ$(>LSvY`YXX9(30QnmoYRllJ-Jt zjz8O_zgW0kIS9+H&459=Xhty=rnxUF$O)lHb}NXFk5}Rx!t&SzKQb^uthz-qpO86~ zeC`3l>LB(9!Ck*dg;{m$|GHHEpMAbNkB>l@E*E&2srZ-r#VCxMHmExfV4NBy!)KkO zYuG>({$UUJmm9irW)5QF3|w4YSsq|Mc{Z9ggT6c}Ds#;aT;dZcO9!;tO8YxZ849LYmpo$Xy> zBn0XQcDB54wLhuNMfUu9b^V#ffu<)8YaR|C@Qn(b456^Vc%db+aJ|uCX5Uvl9Uwf0 z&`;h4Y?4a{p+3^$a~;VpP8#(uti#`ouJD*>=CIO+1d6fFe92*xoOp8S!c@J-z`eDT z9o*qcH_43^r#W-=K5$e=7BhXkwKi>BqDkj5O*c+KIE*dj512Ene9%GpyY5uI6tyg9`pwa;hTbgSo`NA!k?c);z86jELjt4(?B1WIqelA2>W z%CCEc_6Vu=$Xlsj!8rpP>1)C`DC_VpLe~N}#nhGQ?fygL}#u<01 zq)(QYNmhFr>_F~^fP;5suCMPr93(vFJ<`$Yr96MG`)ur~Y7B<-LBS;?l zSaI{RGlt6}v?C`Ru%s`kP`jNE&Vw7f-ytm6Ev`!IZb6BO)VD&)r_pptI+A*=FCL z+e?PyM2LtIa23!_`@H@i-rfW(=eAw@zRa0YA{0_Wq%vkG)txz#v5`uKP>GNxWe6!G zAyZQBkS0Y*C?T0D5lu)m51KSj@9*-gwYKMbzcqaCde`>d+xHW|Ns9y&*Rwl z{n&TgcMJp>M6}z#I+GM|AolL<+uL5AytLpP3E~$oX496YYnX^(DCgSV99^v&SGiL= zhvLA1xl<@LxU6{gY#F!q3`TPI5&juCfAh4El?sT5=X*(tlP{CFd;UXAG30U|JJ$20 zv^L4SM_9}uamAAuOXIj*s~GjDoiaua)v1iDA#|=UB?_ZJ+ew8KMkr#CRaazoP2<32 zkKaxmB>^hfk8L?FTIHu?a_!u?O!{RaWz#jy-s#}??TY&g0%!GkPc+(Vo-@CJ)dtb+ zi7mw@2(b%l9s2fNhH!O`xmFh*T{vNsK8&pl%ktQD#kXJH{jx|Px2P=UoR=AHr zoarFE9Qi^*)3-M3N))^r?Yo5Y9d0dK{I@r933P%#pawmdNKf?-uhj|Ur#oieUWM7? z_#okA4bX|i1Xtm}saQrIndP~UX3d;f=EX}vu1!C`I~DnK3G~^}p${MSWr)U6{ag7b z%|(kBC&EDNtML5hSmMtNBzWs?svR!jY0{}^Cy>>hT=bK1q&J4M<4;~I%Z&-;i5@_- z9coOZx`h24IeZw&$v@zk6HDZL5QuF zsQ6q}#I%#2p}T2hb#BZu4q!8ff}=wf^#QOSBYPewHN&AQ#*_j=c#WoPeAZ0=%t=c< zv**pZ*O+Xf7sSGyJwQ0fhaJsJZ<%~Pu=v<-#kI{yj>69JIYj&Qdy|%yLrHO3lAsH~w7$me#(2$5BS3F~P}00Ur|Dk0q|~Wn5At2F zbY$XpW}>i{A^$qxaR{!>%*+~%Z+*1MyYSdH-?i~p$hf3j7jR4N2hKJN3X6+xe%uaG z!dRTQ_n?Y7@Pm}y4sL0Bvi;@;Uy`(Wy)~EVgb+t0?fX~2l1Z8Gl1M? z5mADsReTjnHAG`u~$Q&ZGDtdKKVrtgT zE*e1yxxN%{fbRHgl7EUgK(l?9an9TQN!2g*%ps-sV7DI3xB_wUZ8}i%dh&zI8hXpc zQ)e=)Ii%kl@07fR1AVRpb1fnha_c`CI18C@9`VV8_>B2TheOudV;WD^j+nT~mT{5> zZ1U_$r-s+s=U53VzHog%BG2smql3wJuAVpA+kh_~`AcsBNO$VxnbI>#Pf^_Zwe2yV z^#yoKh;V)L-|tWBVS-m5BYf*7tz%S}--b(&Gc6OEk0Ak*)zl^^DBOPW7$pkZoSu%c zq?9pT=A%t133>~+e(@xrE-Y#H&gBtQjRV_DIq#5z~a8>KuEp-`NHk;bp zr(wHl>L5EwahWsmb3O{0&!6Ae@#!fi;yk4)CumE4ODAQs|BC(1H5(6UEmcYfcE8#i)5;i0&VCV=_0`P$lZ_n$7RZvj4MRNnea+$vPR=)JFAjAIT} zdC`pm4nMD9ZlhlW}cMB1IK47L^khq=l`A^UbMtJLhW*gMQ47vOdo1 zY}s*!W@8hRxnK7;> zZ8^Vv-rmw(t5y}@rImd`k=CZ*S^DXbb1nx2G}XOTxe}|wfv7lnaz)v(W*@(sc!FRk zGH*{YEfThl2*a;U-PGDg#-)ZlAR0mai1l-1H`W3o;1ni&jxj(J&p_2eRcNSo9r^u& zAySj>n9lR?VEiok$3-f9?$pM@SK{EkazVIYU27xo$<4)Ny8sJh;$JP2ur$v6CUh;i#Ob=CT72z;exR+9+<@zrH1JwP@vxYd`X%*qBD~ z9r;;pc(T1S#e)FjiGU}cIVoU;AI*xviz{#?>8E7zU|~b}yH!cr1{Bx~3w~gy%Q?rf zwS4K)IkZvR#fAT^tNUr&j4dIuCV|O`jrNBR>@|5X-oVAgrnRJ|s)zKhBIU7E_P(!I zuU>tvwXcJdldA3L+Ok`FI9{>ep?&6Tv#g(+JM6dArIdKHcj|&$kPXSEUHhYrxPuj+ zJ4*1dA2Y@O_)Af7vq$#2ky#)l`XQ^w&6?GO7Rx%{Y>e1_n9>(=*G5|Orf{#jdi4*L zeQ(RW$^fg*XI?U650pF0s5N#lbML<*CGFQ;$6i(9PN&|T=?cAXyS62ha)82xr9Cwu zeOSL<{IM)6GBUW|tgq)^XpBudoVVltpf@i*i7H+?S9E^_Ms3nrZ#oOs-}rEGr-Dt+ z;~^qF$xLx-+*|bQvIr=@PEp>V#?wVL2F*g#`2lb>GO1U(nuYD6r{$9 zJ2XOfe%tpshBw|W3D<`+&Ux1p0T`3J!KeIZTIrLmy?vAAyNlw32d~b}#)O6y2uDJm zU-B@s&5^p{J>^zUI=^*C|M65`F%y-V{in)IDNebZe>KO>J0(+R*SSdFwUf4_e7NeI zDCUUpw|v@%9WjKI1YhdQsIT?-L9VT3w~!S0sWw7tPxb91oQc5DWBVqxy`|=Fde|jN zR8Ky1^mebr>6Oxkx!IP=$?ctSE#I#EFc8sv-JT=}S=*YKCh4=JP{C1a0gc($SD#Oe zO*&M6(Dy=Iva^8Nia`lzapT;cmDa5vMryjQkIeE@Rh=^B{kz9dG^n@bE%h1H2PhChcj-(t<8x&W4Gh{R4U7CWSX-p*E3v|Dd=#x^bN-b9YIEB(|K8fKN8_`hr|%dY364FG7Bz0#(Ijxk7@C)q z*469iKkOeZF1qcgl~(us?w;i52M12wGfv#_{73bpsY80R{whb!(VrjqqwJ`<;P_g( z0pQ9LMp{jXpK){kk4mu_&WkTRQBA#;{PJaPz0QKs2j|2+lbpOv+3()?n_fK+ZUUk` zSe0#K&{r%%2sr#-VgUc9|1!}B=)LCG>%pgOGT;wFZop%~#@MjMbF%9<9lLr_JMig* zLFSsE7WSDX1+!D`^%y0t;dpm;R?F&~mb|^M)Y)3Ntrf;`L6mzDq2J4|)=l-TJn`n~ zoa66$+-hGsyQfQ?xe;n4vq^0QXFz^d^l&uS{J++F{eR#&{*WIRS>%6Iwbn*RlO0U| z4QN^L?8BlzpA@YA+>w9%{Qm)AQtvf+Vc<$bXGCfd#hfaVDj&-o$4GjGgB1yG&*n^h zqq7igFrz3bI!EVkKP{u#n6daCSuok*d5Y_^dI`c5<>a2Fr>CzDHi|kRhFI>&v%nb? zybIPQx*jPl-4;>NU2wW*iZ2Mve<11N?))n$l6H&Q7wCDGb@mzPgM&w+el)xm2)=6x zbzJ`$Z2zxV6l9vh`)~d3*Ov!s4SD6bJwkuXKRAnujH~JBUuZ!xk!a4{r~Qz)iP?ki z54xI)3kWFwW6Si>fO`Vb|C0ysyLI>OcNi|zP*Cvr^L~>$z`_oSpe_4n!s9=E$&xiI zugBXJ>8w~G3gsi{b3}cYDj2f2=Z=OHnM5J6vTMr!PE}0*ujr_jK53T1`mMELDV620 zCe^Hc-2{sd|1-`tTN+m4>C>k==hfpPL zF?Ni65Jd=(bag z`Fx+VBzSd1sQKbQzH%k;l9ETbg{dIrE(Z?a^_9GDq2OuKm_b+1WNbrZDkP6a{_9f& zwWdCH5CX0^l+KO#dwgVCM>dGALW2Rnfth7{k0$d&s$T#>T1v);u9Jp+tsGG@Y#!~( z(?dtV3^bWO?`RtYP>%{1KHxYPiW7nvC4x$=K(R2$J#=Ut;Q)-Z&2epyqe~TvbvO(@8RHqJAS&#;|er;zVSn@j)>&QL+OxoT_yM*<6%o&oTabeS$NpU(UVZ-5H~P+w=mfoBKa1wv?_Cd#fb^37`eDuSFKU1$`t|J_(wrv{n0zSPi}<<|po!9N zeXwU29Sp!otbJAbxH^o^kJwCyM}t1AC@xMrhQb{;t3*MlDKN|Nx7$9mU|PE${#<;B;px;HYu06$cJ%j9xAuG48kswI(;9aYWm30*r0 zVDr-Qxt3`Y(2Kbhoj0H&EiP)%Gdp9vVaUjl4Axy_`@oAz>*=t5UzabtG?v`bYw<$lhL3}~aAOn^5vq1{CyWs0RIIvjtTIMF^lS8%Whd%%EhE%qoK2I9 z>%M1(__Ab47fYin5gH~g;MPf<62_e_ICrU~xg@}idFqi>E{Z~Ljn<1* z;=hRwrZZTum+t)Kspr6b(&u$%+E>OwUCr3}rIc9{A=?HL)a(jxz|=--pTk$D0R8k0<7mJLJT}p+5Ra?MrDH*V#`P4loY4e}3!~ zEvhjb7hsd&s$ai$vU|AJi7L{UMYZJF?JKS`2fok=KI?&I@7Mdl4n_<#x?FbOT9Lss zVf%RM`^-<|^5sVjgDL|=G>w)Ibdc?RqyVfD^XiPUz05NMqHX&if7L^3#z-?p)47cSg^^pmJcx|4?ThC9LE1_-hjReV-iv?w+=exDWMG<-tQk#TDh zK}GORxa+Iqp`fX07OqV6))zKsln|UBj78$`$i`Xq>lazPj9Nb}sXubWD(=xEz8Jhf zBG4gnTK3E7X)25((EBq1{h^`(N!`raA3rUxVY11A!b6U{@8F7C({&-atAE_1WhN2{ zP_W+NF`teQO|Fx;Pe6d}`AO@5i~%f_6(3I&+>Jmi$!9=K8hHJBO~#EF;Y+DIoY7e~ z1-&j3M{!75FK?mcJ9f?+upI)9>1b{?Lh^@jTP5+!OnG^Cf{Pv0pO~86SX5I}U`zZ> zMZ;VGp3Xc4F3t{i_u0?zFf0i!5JSTnqu_f|yL6S;3 zrne-eq~_wNP!ggf$UX`Xh78y;^Y(tAzFA0rxpQJZ-rjnqrlZw9;==%Z^&3kPEUX~T zF|{dPER(7#bxJ!h@9ek}lh(N|KRH|ZSNO+U>3|Qc=zJ~+jZi{kt`)!(r#ZM7<<0KS zz*7Z=%7Sw#45H#S$a$M{BSjHF=mGo>X4KJJ-s(qo`fPA|`Lot=(0d*c;Ev5Rq?jgw zyNR_nS`9d4C`)Fsx2NacJ$r--UEFwrAF)A~^8Mjg&}G&l zmFx25>+NutII2CBeT}O#)N2#kId()#0QG6pemgzU8uNwCh1IrzyrK-6aBkS?4L1R# zu|CU;+uh8=G+4qf{?Q}Sqp>S+Z7K3IGP(pSQPjF2e&qUTqJnfKMMYtHgYfnH9YyKJ zwYoN=><9@3VnU$D*Y^PzhOIC^E)au}r{N~EZUhCz+AI?tG{|yR_{04wLe>bEJE|6T zEyVkXmM=!=TV>x3G!8e7+vSsF{%W*J zcX2DF#W|c#cD!C8-BEfY=Vo6#SYDbA7_ENM1Ecb1&pNCBUK+Tvxv*-$)7&#pL{*63 zCTazcee28II32-vW;;YxFkD2RLiB{&w80dH&c92sae4gV1G8p59lsBV-Kjf1Xt2*y zBr99fPI^-^hS>?ek2%vPZOq)cxkLJLlO_albuK>>a@Z*iJp_vns_zveox)GWpqJhDw=Z+Lik%-&Nxzr(S>7uuYJrE8ow#ae-igyRh{Bf7S7Sgn<8hasNO5#a&w&z*&9i zM*|4^C9>ZS?j zZ8+IJN$JRl*xA=G(|uNOe6+N*qTkz`_>w!K>!(uj8zfnvK_yhiM`jn?DxQNn(g1a8 z*xKc0Q}n=yomY)r=yic|D)`iv{z{!O51HEp*l3}udUth1SEZBYgq?{2H*JR;-*RcH!_GiK+8>jW_uda5yaX-rLq1>eM zqaR*IAJs5&u&awLEVL<}RCN$N+S^|fFT8B2KUlTO?%V96{icQEAyNM2$s#7XCx?AQ zBCB)dDMisch8y+LaWAy7uF93&TD;-z_vv!RcgbT|ozo)Do_cnj z>et;29)Gl5eATpe{9%c%V~*;k;l?v*@eC4ja6mQvwea3i<@L=*wTDn(o%9Z1JpAh9 zLCc+oZ^^Ug&?z)BV5&C?lE`9{n&p-)=T%kmB3~@}`wJ); z9>xre2F%$)FA{5`uRf|*;>J*^sTT4EUdjpzIOYXk8l~p-yk&gy9K+G5>zXreqy(QL z=1C#TJt8dcVE6G~mUlR7v4jY+fB_ov-B2fyCkctj^yO2fqg|9J7D!a^)Iim+iQ zC^!X3Tq%U)=lbuY@T1q+`SIq!fG)FxLMcBr;ONoUc*ULBcQ+bM`u@gpBxx7G^cA>K zI6XP-Vd_$wOchm9xY>|`=D}R^3s!@p0FL;3Z5;r zu0-_3L^If|jE311o804-(U-?98w?j&OD~M+`9;w|LR_9&eV%iIl(gbCs7#=pH_?0j z=J{(yW-XKd04K;~2J8<xR)hhnsa(@Vw&| z4;@xUJ51q112YPrtD-lyd_Nfk_)H;-ecO5Sy;b2zE(jWSzBz~9kbf^9BN!5g)0b54 zB&t1AXgy38YlYBjwUSx%5dH961OsRgbuUeG{P6c6_{%4(F+bCC5)mVnHnc9p*nMI7 z!L-dUZ&vkqVk*?8RR2)Ky&IBRGLk8a|t^#K7K5RJo7baL8&aL{0 zJS~2FfR~yZ8-mr{H^&8EW9IJH<->@gIq}ad;!2-x{3`TjV~?__|@QKW9x~XipAzENXZX z7{|#Gxru))V%cv`e&RVDFG;}FRti?CSD!^U5ZdU}YO|*iGRmI!NauX#B~rV>+RjHf zuCic730txH^h9(Nb?gn$zA94>nMQUI>NLdj$-Z)Ttm>dE+1^&1vo8@8+>>UWjkis1=ZChWo5x1&#SKx&gaAE)x& zdt+C(x^KjYnzTaBbP9hx(fn8?$CT4m2zmHSV<$QOwH{kPtUsG!La}uP8-4fXS>(xV z*`C9?3!{KpGU4@8L57z`_cHt6P|%E{NUA*b6;M>YK5K@(p7IqLzQ$Z<0wh;+GX z%EynP;C5g{>q2{@nBCG+r;^xO{ua*mk|Z&xjm^7XuSq1Q{o0u`!p63xxtV#YVji~W zBB@hdVPVw?Z4pil*XgzETH3G7I$z{q6_y3eQs62t`X(!@ng)GbZx;D1Sy`OTs(2js z{z3pc>udHt-Uk8^j^znK3?Z0kfV<4z#O})bAI&xZ0O_KUZ$3q+Q(NZe_fF+iuk_h;Ol4*J z>35}u4xK0_f?W`O5swIo08sWG4m_|tksO;#B@Otq0HU`^2WlOstJm zwKmk!dP-MIue6(<&8@BK0nG|CoDb+NFhg1nW_9bZXnz7JjPW{R9Yb+Wq>btEa0wO5?G5)3?7OedaqCo&QA#)5X@}67BIIlzalpL;9SyV4EggUK$b`#4h+iV?q@qCtacfaD!9ycY z^rt>D|M>H@lJNrkf5mZ_ePlR!>xTJ&d=T7|8{z#jLe~6lsMyZ^FpHM|OJT@hdL|hL ze1smDP_ z`uUfK_40#XgLKg3CzsUcJ6aar)OCaXjpUfnz3krAT5*hah|i8DAEvjlyrCRsJ+VDH zf_GiH;uY(KHgjwcw+Cq1Fl4QMYX`vX;*-Nc>6!sxKHnDq60&CbLp|5zp*v11Dkzke1Z45# zqMPw%k`rAWf&~;Q%tk)iJ^yt{e;DKR(?j&{tlj=4E{caJ#@Gf8+!fVueCnMVoR}zc z4Xb~6czvUbZ&zVah_E@6WA^+0eRwQ6oux~o99|DivF{caaWv*`)s3=rfTGF9_h0=k zIovA%!{=*WPDbL+Z$CFnYoPx+BFP6 z72XYbiRkqZtfyWY8%ion*7;~urhH@OHH*FxSNarM?2i-9w=(bu_IAxl|Ms4z#OJR5 zWAG4RRj_1zOXE;w|2}aF87EGAU8G;Ou9?uz(grk>KVRPD?kMR3i4A}cv~ z!#*}wa^)CJ+08iqK*`O^V8uHh#5Q(0-;1@I)l#Ki=HH_teBZRj#A>T!N_ARtKC|<` zU6^mZ!@rIf74`X@mh{*Dpj4FK{&sKjuf)t*@S%s{kaQ#eeBjCyIuk6+0$J#t4{Hr#{16sJ$>P*VWT@lbhb$0qQ7yyLE*%jj{CFp(|f3Y z@RzWNx`<2Axk(stj~{=m!r0L3XJ>2Jl|C2_?ADYzeoSxl@HXoae^?xW-l3n+c>nAP zBy4*BX(Z@BWyt(gh&0QlT3)Anpt$Je+5TM4a8FH+Kf#P>7b&{cFuJ5XzxnBeJ_4!=I7BYNJIK(V;q zpxgQkO~-@R{t~o_Bj~S~bWl;K#*d5ObM6|Hb%XBz-=$#1Gr9yLyID1=UsWOX{y#FJ zI<4k3;@v8NAcg<&)&=`*?KZ2}Y}vehqsn%hjh0(x{xPCTSv5Q2-!Y;(BM@{u`^ShX zuW73fKizlN5~Do=f#?h2|GJ%56ovo#hxpXtYUNi;!y#Guy}#c(AAIEZH|Llehb>M- zZ9Fml^6jXbMyoc3t$M%U>9rz(FMc-PDZCUUf@^{Y0OUf=rm@9$MuxOSld zSG9WhqHGx{jQ|-br?naDc-GZEKNGp~qo5!P+^)%}-{U8U9XWxNjOw0yh`V0CeA!bO zX8hYF{T?REmc7l&YMQ6tz53v-Tb4}G1f=I9_`&68$Och?;PLeAah-o<;KU;$GR7S! zEi^iMbw-i!cN}@z`F^?o+)oJRqND9zpH22;9!>CVM9;=P9+zFjq@*tUJ~!A4JzBGK z%w0{bgoFgP{;}>ZZB?`W@tp&EHXc7AwV5Fg{!s+E5;*h`%a$3Bj2X04O#AR*SN%Uf zFUrpw!f^4%dCuZa_Y*wrBRJHN6n+2x@1~{+?mc^jule(hvHbWn(k-#f*9b`5Im-TI z55arsj_zvEjZ>$Vp`G+pcJIAw)KI~}L5ouQRZ{S6N;-TL-IL!Ec_n+~4iqsXHC&p} zJ$>6_Goz@w+Lok4BO@#cS1g2I6?B>MW1JSpjvl4)OavoL7R4qcFmM%Cw+o)_kABGk z%B(dNYoc%PzU7${P5=6uP?&|Tf8Z1u(;`fX7{z)Z;@Lr~b)QwfKkD$^9u z3f+vjad>QwK=AuhRCj02P29#v+U)Dwa;@K*b?eUS1Tzpd={!F=spmdhTsiZWOvckKfFcu9#BktN|kR#%h1jlOn+38-jx34P0&tEmbcE|7`SpawhjaZ@Q7@+3IH#b{Tn}g+`$ZlNDL zSJ(2kUyd~6ePjzkqu0KXl$6wPy{aYh@IagD%|$K&932cJ}%DsP;A6%4e#1@IUIPDs4w;hsck}LNxKdnUZd7dDx){KL#0Nb}JO_)3)zIlt*}Z>% z1A1!p-Z^X!gYMfdKmfp63ch7|?!tw^+6XAWmzPteGfaeBc&PRgO!#zlQ%TS^8+8kKiMgmS*F!Vqz7BpCb1G`}kKh;$AyZK8X3SXr%Gw+>yx{=!!o zv~7@AAD%s1FF$zbkirKQo2m~A1BSiMD;w4Ih`7;Rprul!hS_Tl5A1+}9Ua(a4Cswi z8-9}Qi%wKH^>XLVq#bE~&{LsJcz1(~l4ZrvgiY9wN7V@jU@ch)57(cLj;`a5qGn~n zBpX?s_A4HB5m}-)U^zl;y6;#Kcjr!dAD7Vv(r_;!e>KATLF)vT+OhTP#@L$gnLTp< ze((41^O;Wz4i8T?pTg*eIoQ~?ZNv_(Ii!VNv={9-_Ro^lX%Yt7V@o#2o1Z!+k8T>rw9V@h{O8uq3)BK z-R!G0mMlSGz66-mO)0`Dt*d7h0Xsl#p%LC=Z_kSn62^{VelMrX#Mw_Mx28$1nxAPA zRq8+QyQnQAAFM^)B})v6@l{EzT=VBqS(JO=^7(uBd{3Vi4M<1syLbQoSyQHrao-c2$tz27}Aj@B?VgQ zsqEP^y=6^e?*YT#KfG50OPSE+x_ir(;3e`qwr#@{>j#5IT@NJpX)if&U_Q3ZD-|YB zoVY#lR?yGu#V?N^JNCmOSRmM2t5LkrdsJKB1{ni`VLR@`viT95D~msz=f}2prJ=lwcDL5qw(`hiK8`He}%a`c?e>`5jiS9KObn(bZMD<4r`|v15xkkSgnbE?k(L9$<+Mg&9vL)Yk{v zT0s=CLQ2I(P-vYD+=zDil|__@ET4j$@NC$>g$s|uR@!%p>YS}SgPDm8M9*|C$32nEd<3l_R zXxG}mGB^QQeCg5%S>AQ*-McH7Eei#+ApOhCW$IKVCG6NWZ?~*rUre?DXTXTv=7mQ= zTVuz*ld)tmJ%0S<>C@}U>S=G^vbaI&s>@~fyYK=Xxc8t~R^G59#q0dmE%U~@&v5qM zsMK{d#%0Q&o>oc5b6**MOA)yuMkr@S*g~`;L`oXnz%l#6h0me;Zyig{>|#F(4>!A3 zD$I``(hm!88ylMt|0?%0I6#we+xtjH@UmeoM{lzPLqcNoWoHJoYmOT;W(-Kf+__T1 z>r>i1{L`nH)XTY>+`q2iB!N){yCpseOYbQgHu&=;-YYwWH-s_sKhb4B-Ukr}PgeTB zl#Rk##wM^SdG^d1GZ?m?vE_}QuI@9?P#V_-ix*=@j2!=%d(W}qW^eQJ&DN~(O)vR* zi}~icvz@%}Zjm3nl=FYnrW-(15fKqVZv%a~eaeP+wbrbeK5pE=KX3AG>AYtbHcq>w ztPEDZ_b=nFHK5`gpBz7^zSasWw)b}i$=@xoS(&w}Z$IPX6}MSOh9cHu{;3WcCD~nC zL{?sC!cFjn)TmM6rN-36z4$J7F|-*VtKw9OQ(pBJlzNC|*9_h#e6aAp*IFM zA8&p=h+|1utA5u^{qm*VR`d7D4IA<*%^r2yOHh$N?rUo+)3SHxKKF2{sXR}i)Hg8GY&rer$T?s zUH*QX;Lt$Y#U3Ko{JoTxIj{WR`jP316_edT~3GNRWF(RaNu;ANN=1La&3EoTgRBe|AW#S5YMs)wa0mCv+m1tOMVi}$dS|)Iqck=)GlOAmP>rc=kc&b9Y*wKk9D&D6~DXOR#?3ESl zJ^J+6>21{aM|vON49pDc$d0<3t|4t^WR&9?`YYe8geXug3sgMxcQWv z&o*H=on@N5J2MbCbZu)WKd35cVqh?9_m97k)x_w1Qc>Cv|1Le<_L+M+K(iO!N^6@6 zsZDy|TaO~n{fXQ_tub<*RPLexl4k+GV0&WsPZh{a>Z!VSL2`03OB;g+t9xeZ);o5b zJaHlC1s@eg8!?fjhm{{5NDmy3V?6p~X6EsWMH!)y$8ROr5y z|H;YZ+_h_+NO?foA7mU)-HK|!=XUrl5kc-jvx>u~Jzj67ovTvdN7j=gq@g+4;wx_Zhv)xx8`VK60%n$Bh@gZ zzR1aW=zn35pyycY(!25dt5xxPWt^c%Y5&2j%uHO$XUS}Q6dzC4Xi;&$%}Uh0%v`CF zT>_o=2GNX+*8<^`a`{$@AFRIe46W~N=w%xj5pnu?!Q*&CNv~ikyGPqCEZWO%{hTpl z21S3{&i-eppb9H0LL(#dT|)(elXnBh3d59V&SV<4w#f{+_f&p`>Re*Ct}?AibTsWE z)FSR@7^1tMA(+L?HAh-T#%$%vDQ=xEHa1mf&wl99r*Tcw;C>64S|Ug6huxfS-@ZZ8 zXRcgFL;mU0Of@x=*mj;Rra>oi+MPJjm!39r$-IdZSK&pL@^jUJgw}FJOH03A{oA)R zvD{Xz%4n)i5jiTS5(zxs=+dy*&&$&jTCKIx2#W)oZQHI(*;t1OpSkibc5NQ4H>s&J zRaJ?^9Wt|&2;g~6Gk)_pQ`=lp+kY)Sp7t)4lGMc6l@ggq=p8$D+`d9f(^ywNbN7#7 zoW3YS@k;{NzTR(zj!ygmwL5Wf*_oLyXf$5E@`zuCiy_l>RFhTT%~iUj$i6-Fdb5&g z`vDRW$ZloQ`vgPl^TrPPQB0qo5&UTXu5q1_47M+Y`m<>SkAC<3JMHVGPTi>FVWLO4@)m~AyS=NKV{b`N=ASGuk|H4!k7}q zhEaEIcD_e{W>;x=k$d*=p^=^42b7&OhaI&kM=)fd0MZ&m?p!G)J!|#s;K);!)6~?i zB$<=gv1V39g>=uuUutUZu}-OYWe&{n5W|<2Ue6VtdAx7Poja|Zjtn549M$$3gSx)5 zWD0=*?AdkKZ%`%M3-G(L@%=Mmc;7^rJ`q-lZJ}CY44=#$gHheho5l+l&SZS^+qY}& z8(du4xw6^T1U#?XzP$`S>zL@MX*}{f-rh_|2qaApvmgpwj0H$>&x_;8@jKFaVu$tUh^IkRf@YU;KelUh^(H2HSy>N~EzRk=gyuXLj?$F} zuinn5N1<7yCUW!{mP3#=RQtOht{?9^LRz|s$Joii0YPv;`JSIIQ{TL~6Pa+ir8Y(8 zZR{yQ=e?Q{2Sq)lyvxhVa8fOQb3T;aKe90}&4{DYhXVWT*};BqtFj{Vx!q&sA}lv= zeic7);%@$C%Pj9v{okgmuok^8T#zJUc-L&=8HJ?3W8IKXNvWxySgvt#7sbwAz4~nP zG!~5i)vM>upXURL$Wk_H2flyyEGU`P`W8~Hq`bWKlfiVpe*WA!4D3;R6y5rpSx?|7 z#%@D|2*AbV?Dk*pMPD<}*IzY?{(=+sH`iy^?2(A0M33V?xn#7r-{s4bOhmkI{T&hU z&AN*7i#uj|zqAb|U5`u`XjwE?wLhvuhOQ!|oaSZ2`m2cWeP3H!TMrmGFeWQ;}=xj@n9r#QSJ;?xxV-{hA)=JzCvCY}haYmVdHN zo)zqhTTdx1G$kl(6B?F>g;`^pRbF1sVjeiy-;x~()a!0V0=-c-M`w3ePV9pr#DZ>XiJ3rz@QZF}b(AU3k z>{$Gb8}q&1mejWhIoaPIch~hz-8`*GF^lKz-o0hIx;I|_c0a?kB?-6XTDF7;K!9`A zczVZ6CSFxGFf%hSFlfs4y@UA@zD!*6i_32wJ4Qp%f&<2tfqDZ6{6;ylc@~S43l+Ed z_1Ud~8hcCc3hhXez0^%it|T4NBq!?0u7d6}hpK~vAlrO>R>s)zm@kTm%evLJ_9B`r zv+2|8*_L#5qYVyq{8)I+WuW_+2kMIj-{&7brC(agM*Pyd%NmjP(xtD7eyPi0M**pm zAKZ85+mjYnR*Hj9&|;dHjG6gdQSL{4yY-HN15f4V`hAQhNRtW;#*tctXq;L5%7@(f znvybM+_;#yxMmi=wd!KmOyx-MsMM`i+qT8lUgyb_oLs{#4?w&Ypz$O?-O&y~H!hW5 zGBe%9R-5H(iM&o%iQBKT$aQlW@LaC%M{LWB(*q{tb>++TJ~C1B$>Zo~&y=6rt*mBN zY&ADuNgrlnvP>i+GZUkGqPxOLL*gyj72ScQ_VQZh^)@mzlr|>vYt;hzv zX9GpVn4ovBN0*>$i}irWz@K=@;KlOc+mpnT!>4MyW=|S?0@KYa17&!Z!e}ZruhwajqsB{b~* z5(*nuH-A}QtVEkm+Hf^N2Se?+=&M6AzkOp{54TY%TxYE+R2OuWD&y7g2euiPn#;~r zP*}+FT=W&C@ONUe;Y3AbVFLi_)&e5zKD2y1`~QOjnMRYw(I>LqZnKdUydQ9SV~=?* z^{H7SM(B|zP;~qvB*e#0pPtxPU3N+5yGTnE9bRMi<18pOp*tih>=T})Cr&Jt_A9cO zGMp`)M|k({sAI=^M^BBQ06+e`g>_b6XNp=r#(qCf2yY78LrI*xqLa7e?Px**n@zHcjk5Cp^93A zml$=89WEhUek&`hJHZrMY%Y{|NY@ASo^L(g|<%4&K2e5;1mf5 zIs{$fBG%XYupovF^F#N%cHO$yQWI9RKg-D2$mlm@JpKY4HH1C+_&n%nU-Zi2<(DsE z@$to-9Ty)zRxf`r3%Pvpj~~%{N+Kv7GRG|;0h|g=%YXQys0R;dl&Mc*qoRDOazABc zl%ee6@b1^&D>v?r-?eMsUYsyvE~z@_-UPKP0lvPz2;RiR6!I5OI4u1YVT=hK_U1L*#Uh3Afw(C)N#P6fI ztDt5eUy1sabCj95J~=}O*;A2__=($MX;~R)%GKGsuRbqe>}5ws>p++#O}j#m%a@IH zbq9LiLPS?wQ1I8+x369;Q5ZrcIkSJU>66DOe2NMRGIG0F3j^ye&U22{s`=4eE5T(Z zl1|P&3M8UV;_HY3FF73aBSjsWYYxT7_r8)U>9uh6L}tj|Qn}HQU^v8@XP^dKg4>t! z06f9MqQ?ywK4px0yS4duDW1M4!@VRSKX7zvYiyv-N2mHoQl;}RUEP?#Kp(Ma=AGG> zTXuq!t5dnb3s9)%UW_GJ>oGax3oZfW z4v{7s8pp@auWuf-%8#ll-GO&0sisf+09?;1!MD=ecnR?ntW8%}4eylcvu61vPU|cC zH-#Bf@ss5^|Z{NnQ*f}W z=kmGWYyTkHUkvNbfB!=ZAQ<@Hz~ZJ*ZBC2_&l5Nd{0G2waJ!Aozf0-=$FFXZ(Mxa& zmkSU>Ga;qlK0)|~fe`=Y4fj-ie4a*7kv!}NLx&H)4H#!S=+MBfYgpTBX&vMeIGAt> z^!ld{_TTz2|M5Hg&*00s_f-~1(3++yYAIx)*8OGZHgM#EumRsf#Prq*O2(9UC5&Z1)2OOco)2WL{AN;shTF(<) zHxvSk1r-%u?{*rXkG=lEc=6&}hyvgn8CN|XDj;T_w1_zskJg4v2|SQ!RSR7NTCH@iN%9 zPjG#Ti1k5HfTIQ)T=z3@$$8n?uX6d@^q;kVuP!4*A}Fq0pydN1Jn9t-2-9YE3(CkV z9os=d+|RJ0Wac@o6%X!q@UZONZgV}+A<*GUXwv{Umz$xMwcWgVfk;bdTXp{aErR#m zzN=dwJZ-`8=PpK)B9qdzA$6Zb*(^|0!g`?Z{DSt;8SCvKefbcwyj##NcQH9J5z{ffOeXpwe8b;Jo8qZF_Mt7IKKHuZ?Y1f!l zCMJ)9f-HE`0RGj9@`BQpQ_2SX|ABV@XAdQq`oT-*8OzSdphL*KV(J!Biq$~@t*%HEHygDR_r<8cMO-(~XvgL?vfn)oq+MoFE zQY_scZ34|F`kdR*Z|snMbhIq7Asa-Vn()Qj;og4L}zePt;ii|g-=7%*Pe{cX{X?c0Sb zzu95>rK(2_h$!qk+k4?@0swsf^MpBb92+a=klXT$TIP|YZ$Lm-X+{A11RW2s1R#D@ z{M>ExSFiq1^wxj`;i9T4rF>H}Gq;-WNy1mCJ?6NK16Efr{i3?#YJTzNQ*>u+Ay=kt z`9sjwf)s%j7)M#m@qNu)%tjrZH~4c>F&mrI;YUJe3K5yP;=F|RwCU6J0yLg)z1LTk zP^bCx*O-|J70P_Rst2ou`Xf9M9~(|)MnKQFVyw>+=+DZsvWHF$ckGoGxeDitD4GJ$ z)m*w%)@D@Jmh$e6)6G(;POLbU8Mr4-d2Fg{AIiyjI0uD=Ri+MGhsz$4l`Bmu6DJNF z*zx4%%|)MAuUv_UO=@`DBGQ#317ORyjcS)Q04?a;yX91jN zYq~TKn85VFdOmVq-0n4;Xn45k>u)v8uB8bheCsD$XZ4OJN{IN#lm3c2BCnLX6@LvL zn7o*?%x}8Zr#?u;?-I2O)w)|(FHG>4)74h!1kPmeSvcSOsPWd}!`dy4&WUL%85Hj`rm z1E-Gt`TCL5nx*n*u_tR$8L7Plhd~Bsm{K_J*Q7nhL0nq;(asI;Y4K>9YF6myUPeyF zID5zMkLlEc;SFSrF@sfI6I#hYiYLjE&xM0;d-i4YE)q^##up!%dp@AnI81s =NVU+v6tzyCFr%wOzji#c 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 Of7|f4VG2U-=Klb60