From 66efb31aa536b9f76b3dbfb60f93858a05ac4bc7 Mon Sep 17 00:00:00 2001 From: jmh <2631299212@qq.com> Date: Fri, 16 May 2025 00:25:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BA=90=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...米便签开源代码的泛读报告.docx | Bin 0 -> 20064 bytes src/data/Contact.java | 73 ++++ src/data/Notes.java | 279 ++++++++++++++ src/data/NotesDatabaseHelper.java | 362 ++++++++++++++++++ src/data/NotesProvider.java | 305 +++++++++++++++ src/tool/BackupUtils.java | 344 +++++++++++++++++ src/tool/DataUtils.java | 295 ++++++++++++++ src/tool/GTaskStringUtils.java | 113 ++++++ src/tool/ResourceParser.java | 181 +++++++++ src/widget/NoteWidgetProvider.java | 132 +++++++ src/widget/NoteWidgetProvider_2x.java | 47 +++ src/widget/NoteWidgetProvider_4x.java | 46 +++ 12 files changed, 2177 insertions(+) create mode 100644 doc/小米便签开源代码的泛读报告.docx create mode 100644 src/data/Contact.java create mode 100644 src/data/Notes.java create mode 100644 src/data/NotesDatabaseHelper.java create mode 100644 src/data/NotesProvider.java create mode 100644 src/tool/BackupUtils.java create mode 100644 src/tool/DataUtils.java create mode 100644 src/tool/GTaskStringUtils.java create mode 100644 src/tool/ResourceParser.java create mode 100644 src/widget/NoteWidgetProvider.java create mode 100644 src/widget/NoteWidgetProvider_2x.java create mode 100644 src/widget/NoteWidgetProvider_4x.java diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx new file mode 100644 index 0000000000000000000000000000000000000000..ade413fe86e8e9e5c6247784bb72bdea58e8c3c6 GIT binary patch literal 20064 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

tux-jEL?3he+W!S< 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 sContactCache; + private static final String TAG = "Contact"; + + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + + public static String getContact(Context context, String phoneNumber) { + if(sContactCache == null) { + sContactCache = new HashMap(); + } + + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, + new String [] { Phone.DISPLAY_NAME }, + selection, + new String[] { phoneNumber }, + null); + + if (cursor != null && cursor.moveToFirst()) { + try { + String name = cursor.getString(0); + sContactCache.put(phoneNumber, name); + return name; + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + cursor.close(); + } + } else { + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} diff --git a/src/data/Notes.java b/src/data/Notes.java new file mode 100644 index 0000000..f240604 --- /dev/null +++ b/src/data/Notes.java @@ -0,0 +1,279 @@ +/* + * 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.net.Uri; +public class Notes { + public static final String AUTHORITY = "micode_notes"; + public static final String TAG = "Notes"; + public static final int TYPE_NOTE = 0; + public static final int TYPE_FOLDER = 1; + public static final int TYPE_SYSTEM = 2; + + /** + * 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"); + + /** + * Uri to query data + */ + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + + public interface NoteColumns { + /** + * The unique ID for a row + *

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"; + + /** + * 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"; + } + + 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"); + } +} diff --git a/src/data/NotesDatabaseHelper.java b/src/data/NotesDatabaseHelper.java new file mode 100644 index 0000000..ffe5d57 --- /dev/null +++ b/src/data/NotesDatabaseHelper.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + + +public class NotesDatabaseHelper extends SQLiteOpenHelper { + private static final String DB_NAME = "note.db"; + + private static final int DB_VERSION = 4; + + public interface TABLE { + public static final String NOTE = "note"; + + public static final String DATA = "data"; + } + + private static final String TAG = "NotesDatabaseHelper"; + + private static NotesDatabaseHelper mInstance; + + 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"; + + /** + * 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"; + + /** + * 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"; + + /** + * 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"; + + /** + * 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"; + + /** + * 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"; + + /** + * 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"; + + /** + * 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); + } + + 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); + } + + 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); + } + + 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); + } + + private void upgradeToV4(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + + " INTEGER NOT NULL DEFAULT 0"); + } +} diff --git a/src/data/NotesProvider.java b/src/data/NotesProvider.java new file mode 100644 index 0000000..edb0a60 --- /dev/null +++ b/src/data/NotesProvider.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.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; + + +public class NotesProvider extends ContentProvider { + 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 { + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + 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. + */ + 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; + + 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 + public boolean onCreate() { + mHelper = NotesDatabaseHelper.getInstance(getContext()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + Cursor c = null; + SQLiteDatabase db = mHelper.getReadableDatabase(); + String id = null; + 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) { + 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 + 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; + 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 + 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); + } + + return ContentUris.withAppendedId(uri, insertedId); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + 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 + 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 + ')' : ""); + } + + 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); + } + + mHelper.getWritableDatabase().execSQL(sql.toString()); + } + + @Override + public String getType(Uri uri) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/tool/BackupUtils.java b/src/tool/BackupUtils.java new file mode 100644 index 0000000..39f6ec4 --- /dev/null +++ b/src/tool/BackupUtils.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; + + +public class BackupUtils { + private static final String TAG = "BackupUtils"; + // Singleton stuff + private static BackupUtils sInstance; + + public static synchronized BackupUtils getInstance(Context context) { + if (sInstance == null) { + sInstance = new BackupUtils(context); + } + return sInstance; + } + + /** + * Following states are signs to represents backup or restore + * status + */ + // Currently, the sdcard is not mounted + public static final int STATE_SD_CARD_UNMOUONTED = 0; + // The backup file not exist + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; + // The data is not well formated, may be changed by other programs + public static final int STATE_DATA_DESTROIED = 2; + // Some run-time exception which causes restore or backup fails + public static final int STATE_SYSTEM_ERROR = 3; + // Backup or restore success + public static final int STATE_SUCCESS = 4; + + private TextExport mTextExport; + + private BackupUtils(Context context) { + mTextExport = new TextExport(context); + } + + private static boolean externalStorageAvailable() { + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + public int exportToText() { + return mTextExport.exportToText(); + } + + public String getExportedTextFileName() { + return mTextExport.mFileName; + } + + public String getExportedTextFileDir() { + return mTextExport.mFileDirectory; + } + + private static class TextExport { + private static final String[] NOTE_PROJECTION = { + NoteColumns.ID, + NoteColumns.MODIFIED_DATE, + NoteColumns.SNIPPET, + NoteColumns.TYPE + }; + + private static final int NOTE_COLUMN_ID = 0; + + private static final int NOTE_COLUMN_MODIFIED_DATE = 1; + + private static final int NOTE_COLUMN_SNIPPET = 2; + + private static final String[] DATA_PROJECTION = { + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + + private static final int DATA_COLUMN_CONTENT = 0; + + private static final int DATA_COLUMN_MIME_TYPE = 1; + + private static final int DATA_COLUMN_CALL_DATE = 2; + + private static final int DATA_COLUMN_PHONE_NUMBER = 4; + + private final String [] TEXT_FORMAT; + private static final int FORMAT_FOLDER_NAME = 0; + private static final int FORMAT_NOTE_DATE = 1; + private static final int FORMAT_NOTE_CONTENT = 2; + + private Context mContext; + private String mFileName; + private String mFileDirectory; + + public TextExport(Context context) { + TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); + mContext = context; + mFileName = ""; + mFileDirectory = ""; + } + + private String getFormat(int id) { + return TEXT_FORMAT[id]; + } + + /** + * Export the folder identified by folder id to text + */ + private void exportFolderToText(String folderId, PrintStream ps) { + // Query notes belong to this folder + Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { + folderId + }, null); + + if (notesCursor != null) { + if (notesCursor.moveToFirst()) { + do { + // Print note's last modified date + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + String noteId = notesCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); + } while (notesCursor.moveToNext()); + } + notesCursor.close(); + } + } + + /** + * Export note identified by id to a print stream + */ + private void exportNoteToText(String noteId, PrintStream ps) { + Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { + noteId + }, null); + + if (dataCursor != null) { + if (dataCursor.moveToFirst()) { + do { + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); + if (DataConstants.CALL_NOTE.equals(mimeType)) { + // Print phone number + String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); + long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); + String location = dataCursor.getString(DATA_COLUMN_CONTENT); + + if (!TextUtils.isEmpty(phoneNumber)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + phoneNumber)); + } + // Print call date + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat + .format(mContext.getString(R.string.format_datetime_mdhm), + callDate))); + // Print call attachment location + if (!TextUtils.isEmpty(location)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + location)); + } + } else if (DataConstants.NOTE.equals(mimeType)) { + String content = dataCursor.getString(DATA_COLUMN_CONTENT); + if (!TextUtils.isEmpty(content)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + content)); + } + } + } while (dataCursor.moveToNext()); + } + dataCursor.close(); + } + // print a line separator between note + try { + ps.write(new byte[] { + Character.LINE_SEPARATOR, Character.LETTER_NUMBER + }); + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + } + + /** + * Note will be exported as text which is user readable + */ + public int exportToText() { + if (!externalStorageAvailable()) { + Log.d(TAG, "Media was not mounted"); + return STATE_SD_CARD_UNMOUONTED; + } + + PrintStream ps = getExportToTextPrintStream(); + if (ps == null) { + Log.e(TAG, "get print stream error"); + return STATE_SYSTEM_ERROR; + } + // First export folder and its notes + Cursor folderCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); + + if (folderCursor != null) { + if (folderCursor.moveToFirst()) { + do { + // Print folder's name + String folderName = ""; + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + folderName = mContext.getString(R.string.call_record_folder_name); + } else { + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); + } + if (!TextUtils.isEmpty(folderName)) { + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + } + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + exportFolderToText(folderId, ps); + } while (folderCursor.moveToNext()); + } + folderCursor.close(); + } + + // Export notes in root's folder + Cursor noteCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + + "=0", null, null); + + if (noteCursor != null) { + if (noteCursor.moveToFirst()) { + do { + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); + } while (noteCursor.moveToNext()); + } + noteCursor.close(); + } + ps.close(); + + return STATE_SUCCESS; + } + + /** + * Get a print stream pointed to the file {@generateExportedTextFile} + */ + private PrintStream getExportToTextPrintStream() { + File file = generateFileMountedOnSDcard(mContext, R.string.file_path, + R.string.file_name_txt_format); + if (file == null) { + Log.e(TAG, "create file to exported failed"); + return null; + } + mFileName = file.getName(); + mFileDirectory = mContext.getString(R.string.file_path); + PrintStream ps = null; + try { + FileOutputStream fos = new FileOutputStream(file); + ps = new PrintStream(fos); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } catch (NullPointerException e) { + e.printStackTrace(); + return null; + } + return ps; + } + } + + /** + * Generate the text file to store imported data + */ + private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + StringBuilder sb = new StringBuilder(); + sb.append(Environment.getExternalStorageDirectory()); + sb.append(context.getString(filePathResId)); + File filedir = new File(sb.toString()); + sb.append(context.getString( + fileNameFormatResId, + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); + File file = new File(sb.toString()); + + try { + if (!filedir.exists()) { + filedir.mkdir(); + } + if (!file.exists()) { + file.createNewFile(); + } + return file; + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } +} + + diff --git a/src/tool/DataUtils.java b/src/tool/DataUtils.java new file mode 100644 index 0000000..2a14982 --- /dev/null +++ b/src/tool/DataUtils.java @@ -0,0 +1,295 @@ +/* + * 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"; + public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { + if (ids == null) { + Log.d(TAG, "the ids is null"); + return true; + } + if (ids.size() == 0) { + Log.d(TAG, "no id is in the hashset"); + return true; + } + + ArrayList operationList = new ArrayList(); + for (long id : ids) { + if(id == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Don't delete system folder root"); + continue; + } + ContentProviderOperation.Builder builder = ContentProviderOperation + .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); + operationList.add(builder.build()); + } + try { + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); + if (results == null || results.length == 0 || results[0] == null) { + Log.d(TAG, "delete notes failed, ids:" + ids.toString()); + return false; + } + 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; + } + + public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { + 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); + } + + public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, + long folderId) { + if (ids == null) { + Log.d(TAG, "the ids is null"); + return true; + } + + ArrayList operationList = new ArrayList(); + for (long id : ids) { + ContentProviderOperation.Builder builder = ContentProviderOperation + .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); + builder.withValue(NoteColumns.PARENT_ID, folderId); + builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); + operationList.add(builder.build()); + } + + try { + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); + if (results == null || results.length == 0 || results[0] == null) { + Log.d(TAG, "delete notes failed, ids:" + ids.toString()); + return false; + } + return true; + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + } + return false; + } + + /** + * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + */ + public static int getUserFolderCount(ContentResolver resolver) { + Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, + new String[] { "COUNT(*)" }, + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, + null); + + int count = 0; + if(cursor != null) { + if(cursor.moveToFirst()) { + try { + count = cursor.getInt(0); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "get folder count failed:" + e.toString()); + } finally { + cursor.close(); + } + } + } + return count; + } + + 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; + } + + 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; + } + + public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), + null, null, null, null); + + boolean exist = false; + if (cursor != null) { + if (cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist; + } + + public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", + new String[] { name }, null); + boolean exist = false; + if(cursor != null) { + if(cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist; + } + + 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; + } + + 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, "Get call number fails " + 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 { + return cursor.getLong(0); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "Get call note id fails " + e.toString()); + } + } + cursor.close(); + } + return 0; + } + + public static String getSnippetById(ContentResolver resolver, long noteId) { + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + new String [] { NoteColumns.SNIPPET }, + NoteColumns.ID + "=?", + new String [] { String.valueOf(noteId)}, + null); + + if (cursor != null) { + String snippet = ""; + if (cursor.moveToFirst()) { + snippet = cursor.getString(0); + } + 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; + } +} 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"; + +} diff --git a/src/tool/ResourceParser.java b/src/tool/ResourceParser.java new file mode 100644 index 0000000..1ad3ad6 --- /dev/null +++ b/src/tool/ResourceParser.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.preference.PreferenceManager; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesPreferenceActivity; + +public class ResourceParser { + + public static final int YELLOW = 0; + public static final int BLUE = 1; + public static final int WHITE = 2; + public static final int GREEN = 3; + public static final int RED = 4; + + public static final int BG_DEFAULT_COLOR = YELLOW; + + public static final int TEXT_SMALL = 0; + public static final int TEXT_MEDIUM = 1; + public static final int TEXT_LARGE = 2; + public static final int TEXT_SUPER = 3; + + public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + + public static class NoteBgResources { + private final static int [] BG_EDIT_RESOURCES = new int [] { + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red + }; + + private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red + }; + + public static int getNoteBgResource(int id) { + return BG_EDIT_RESOURCES[id]; + } + + public static int getNoteTitleBgResource(int id) { + return BG_EDIT_TITLE_RESOURCES[id]; + } + } + + public static int getDefaultBgId(Context context) { + if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { + return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); + } else { + return BG_DEFAULT_COLOR; + } + } + + public static class NoteItemBgResources { + private final static int [] BG_FIRST_RESOURCES = new int [] { + R.drawable.list_yellow_up, + R.drawable.list_blue_up, + R.drawable.list_white_up, + R.drawable.list_green_up, + R.drawable.list_red_up + }; + + private final static int [] BG_NORMAL_RESOURCES = new int [] { + R.drawable.list_yellow_middle, + R.drawable.list_blue_middle, + R.drawable.list_white_middle, + R.drawable.list_green_middle, + R.drawable.list_red_middle + }; + + private final static int [] BG_LAST_RESOURCES = new int [] { + R.drawable.list_yellow_down, + R.drawable.list_blue_down, + R.drawable.list_white_down, + R.drawable.list_green_down, + R.drawable.list_red_down, + }; + + private final static int [] BG_SINGLE_RESOURCES = new int [] { + R.drawable.list_yellow_single, + R.drawable.list_blue_single, + R.drawable.list_white_single, + R.drawable.list_green_single, + R.drawable.list_red_single + }; + + public static int getNoteBgFirstRes(int id) { + return BG_FIRST_RESOURCES[id]; + } + + public static int getNoteBgLastRes(int id) { + return BG_LAST_RESOURCES[id]; + } + + public static int getNoteBgSingleRes(int id) { + return BG_SINGLE_RESOURCES[id]; + } + + public static int getNoteBgNormalRes(int id) { + return BG_NORMAL_RESOURCES[id]; + } + + public static int getFolderBgRes() { + return R.drawable.list_folder; + } + } + + public static class WidgetBgResources { + private final static int [] BG_2X_RESOURCES = new int [] { + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, + }; + + public static int getWidget2xBgResource(int id) { + return BG_2X_RESOURCES[id]; + } + + private final static int [] BG_4X_RESOURCES = new int [] { + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red + }; + + public static int getWidget4xBgResource(int id) { + return BG_4X_RESOURCES[id]; + } + } + + public static class TextAppearanceResources { + private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { + R.style.TextAppearanceNormal, + R.style.TextAppearanceMedium, + R.style.TextAppearanceLarge, + R.style.TextAppearanceSuper + }; + + public static int getTexAppearanceResource(int id) { + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if (id >= TEXTAPPEARANCE_RESOURCES.length) { + return BG_DEFAULT_FONT_SIZE; + } + return TEXTAPPEARANCE_RESOURCES[id]; + } + + public static int getResourcesSize() { + return TEXTAPPEARANCE_RESOURCES.length; + } + } +} diff --git a/src/widget/NoteWidgetProvider.java b/src/widget/NoteWidgetProvider.java new file mode 100644 index 0000000..ec6f819 --- /dev/null +++ b/src/widget/NoteWidgetProvider.java @@ -0,0 +1,132 @@ +/* + * 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.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.util.Log; +import android.widget.RemoteViews; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NoteEditActivity; +import net.micode.notes.ui.NotesListActivity; + +public abstract class NoteWidgetProvider extends AppWidgetProvider { + public static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET + }; + + public static final int COLUMN_ID = 0; + public static final int COLUMN_BG_COLOR_ID = 1; + public static final int COLUMN_SNIPPET = 2; + + private static final String TAG = "NoteWidgetProvider"; + + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + for (int i = 0; i < appWidgetIds.length; i++) { + context.getContentResolver().update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[] { String.valueOf(appWidgetIds[i])}); + } + } + + private Cursor getNoteWidgetInfo(Context context, int widgetId) { + return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, + null); + } + + protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + update(context, appWidgetManager, appWidgetIds, false); + } + + private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, + boolean privacyMode) { + for (int i = 0; i < appWidgetIds.length; i++) { + if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { + int bgId = ResourceParser.getDefaultBgId(context); + String snippet = ""; + Intent intent = new Intent(context, NoteEditActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); + if (c != null && c.moveToFirst()) { + if (c.getCount() > 1) { + Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); + c.close(); + return; + } + snippet = c.getString(COLUMN_SNIPPET); + bgId = c.getInt(COLUMN_BG_COLOR_ID); + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + intent.setAction(Intent.ACTION_VIEW); + } else { + snippet = context.getResources().getString(R.string.widget_havenot_content); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + } + + if (c != null) { + c.close(); + } + + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + /** + * Generate the pending intent to start host for the widget + */ + PendingIntent pendingIntent = null; + if (privacyMode) { + rv.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_under_visit_mode)); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( + context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + } else { + rv.setTextViewText(R.id.widget_text, snippet); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + } + } + } + + protected abstract int getBgResourceId(int bgId); + + protected abstract int getLayoutId(); + + protected abstract int getWidgetType(); +} 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; + } +} diff --git a/src/widget/NoteWidgetProvider_4x.java b/src/widget/NoteWidgetProvider_4x.java new file mode 100644 index 0000000..c12a02e --- /dev/null +++ b/src/widget/NoteWidgetProvider_4x.java @@ -0,0 +1,46 @@ +/* + * 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) { + super.update(context, appWidgetManager, appWidgetIds); + } + + protected int getLayoutId() { + return R.layout.widget_4x; + } + + @Override + protected int getBgResourceId(int bgId) { + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + } + + @Override + protected int getWidgetType() { + return Notes.TYPE_WIDGET_4X; + } +}