From f3379c4080b1e4e4313d4bcf8db0fd04fef5ede5 Mon Sep 17 00:00:00 2001 From: limingzhe <237453946@qq.com> Date: Wed, 14 May 2025 23:18:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BA=90=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...米便签开源代码的泛读报告.docx | Bin 0 -> 20424 bytes src/data/Contact.java | 73 ++++ src/data/Notes.java | 279 ++++++++++++++ src/data/NotesDatabaseHelper.java | 362 ++++++++++++++++++ src/data/NotesProvider.java | 305 +++++++++++++++ src/widget/NoteWidgetProvider.java | 132 +++++++ src/widget/NoteWidgetProvider_2x.java | 47 +++ src/widget/NoteWidgetProvider_4x.java | 46 +++ 8 files changed, 1244 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/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..da37d1fca339809d1fe6fca00a483babc032bb47 GIT binary patch literal 20424 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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/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; + } +}