You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Go to file
pic3v8nmu 121becab82
Delete 'GTaskStringUtils.java'
7 months ago
java/net/micode/notes java 8 months ago
res res 8 months ago
AndroidManifest.xml mian 8 months ago
Lihao Add Lihao 7 months ago
README.md Update README.md 7 months ago

README.md

123456

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
     * <P> Type: INTEGER (long) </P>
     */
    public static final String ID = "_id";

    /**
     * The parent's id for note or folder
     * <P> Type: INTEGER (long) </P>
     */
    public static final String PARENT_ID = "parent_id";

    /**
     * Created data for note or folder
     * <P> Type: INTEGER (long) </P>
     */
    public static final String CREATED_DATE = "created_date";

    /**
     * Latest modified date
     * <P> Type: INTEGER (long) </P>
     */
    public static final String MODIFIED_DATE = "modified_date";


    /**
     * Alert date
     * <P> Type: INTEGER (long) </P>
     */
    public static final String ALERTED_DATE = "alert_date";

    /**
     * Folder's name or text content of note
     * <P> Type: TEXT </P>
     */
    public static final String SNIPPET = "snippet";

    /**
     * Note's widget id
     * <P> Type: INTEGER (long) </P>
     */
    public static final String WIDGET_ID = "widget_id";

    /**
     * Note's widget type
     * <P> Type: INTEGER (long) </P>
     */
    public static final String WIDGET_TYPE = "widget_type";

    /**
     * Note's background color's id
     * <P> Type: INTEGER (long) </P>
     */
    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
     * <P> Type: INTEGER </P>
     */
    public static final String HAS_ATTACHMENT = "has_attachment";

    /**
     * Folder's count of notes
     * <P> Type: INTEGER (long) </P>
     */
    public static final String NOTES_COUNT = "notes_count";

    /**
     * The file type: folder or note
     * <P> Type: INTEGER </P>
     */
    public static final String TYPE = "type";

    /**
     * The last sync id
     * <P> Type: INTEGER (long) </P>
     */
    public static final String SYNC_ID = "sync_id";

    /**
     * Sign to indicate local modified or not
     * <P> Type: INTEGER </P>
     */
    public static final String LOCAL_MODIFIED = "local_modified";

    /**
     * Original parent id before moving into temporary folder
     * <P> Type : INTEGER </P>
     */
    public static final String ORIGIN_PARENT_ID = "origin_parent_id";

    /**
     * The gtask id
     * <P> Type : TEXT </P>
     */
    public static final String GTASK_ID = "gtask_id";

    /**
     * The version code
     * <P> Type : INTEGER (long) </P>
     */
    public static final String VERSION = "version";
}

public interface DataColumns {
    /**
     * The unique ID for a row
     * <P> Type: INTEGER (long) </P>
     */
    public static final String ID = "_id";

    /**
     * The MIME type of the item represented by this row.
     * <P> Type: Text </P>
     */
    public static final String MIME_TYPE = "mime_type";

    /**
     * The reference id to note that this data belongs to
     * <P> Type: INTEGER (long) </P>
     */
    public static final String NOTE_ID = "note_id";

    /**
     * Created data for note or folder
     * <P> Type: INTEGER (long) </P>
     */
    public static final String CREATED_DATE = "created_date";

    /**
     * Latest modified date
     * <P> Type: INTEGER (long) </P>
     */
    public static final String MODIFIED_DATE = "modified_date";

    /**
     * Data's content
     * <P> Type: TEXT </P>
     */
    public static final String CONTENT = "content";


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

    /**
     * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
     * integer data type
     * <P> Type: INTEGER </P>
     */
    public static final String DATA2 = "data2";

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

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

    /**
     * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
     * TEXT data type
     * <P> Type: TEXT </P>
     */
    public static final String DATA5 = "data5";
}

public static final class TextNote implements DataColumns {
    /**
     * Mode to indicate the text in check list mode or not
     * <P> Type: Integer 1:check list mode 0: normal mode </P>
     */
    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
     * <P> Type: INTEGER (long) </P>
     */
    public static final String CALL_DATE = DATA1;

    /**
     * Phone number for this record
     * <P> Type: TEXT </P>
     */
    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");
}

}

  •  package net.micode.notes.data; 声明了该类所在的Java包用于对类进行逻辑分组与模块化管理方便在大型项目中组织代码。
  •  import android.net.Uri; 引入 Uri 类它在Android开发里常用于表示统一资源标识符例如访问内容提供者Content Provider就需要用 Uri 来指定要操作的数据资源位置
  •  package net.micode.notes.data; 声明了该类所在的Java包用于对类进行逻辑分组与模块化管理方便在大型项目中组织代码。
  •  import android.net.Uri; 引入 Uri 类它在Android开发里常用于表示统一资源标识符例如访问内容提供者Content Provider就需要用 Uri 来指定要操作的数据资源位置
  • 这段代码定义了多个整型常量,作为系统文件夹的标识符:
  •  ID_ROOT_FOLDER 设为 0 代表默认文件夹。
  •  ID_TEMPARAY_FOLDER 设为 -1 用于存放那些不属于任何文件夹的笔记。
  •  ID_CALL_RECORD_FOLDER 设为 -2 用来存储通话记录相关的文件夹。
  •  ID_TRASH_FOLER 设为 -3 推测是回收站文件夹。
  • 定义了一系列静态常量字符串这些字符串大概率是用于在 Intent 传递额外数据时的键名不同的键对应不同类型的数据比如提醒日期、背景颜色ID、小组件相关ID和类型、文件夹ID、通话日期等。
  • 又定义了几个整型常量用于区分不同类型的小组件 TYPE_WIDGET_INVALIDE 值为 -1 表示无效小组件 TYPE_WIDGET_2X 值为 0  、 TYPE_WIDGET_4X 值为 1 可能对应不同尺寸规格的小组件。
  • 定义了两个 Uri 常量
  •  CONTENT_NOTE_URI 通过 Uri.parse 方法根据之前定义的 AUTHORITY 拼接出用于查询所有笔记和文件夹的 Uri 。
  •  CONTENT_DATA_URI 同样方式拼接出用于查询数据的 Uri 这些 Uri 在与内容提供者交互、获取数据时是关键元素。
  • 定义了一个接口 NoteColumns 不过目前接口内部还没有具体的方法或者常量定义通常接口用于定义一组相关方法的签名后续大概率会在这里添加与笔记列相关的常量定义方便数据库操作时统一列名。 第二个 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");
}

} 第三个 ackage 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;
}

}