diff --git a/src/小米便签代码精读.txt b/src/小米便签代码精读.txt new file mode 100644 index 0000000..0c30135 --- /dev/null +++ b/src/小米便签代码精读.txt @@ -0,0 +1,1122 @@ +package net.micode.notes.data; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; + +public class Contact { + private static HashMap 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); + } +//这个条件语句使用了Android提供的一些常量和函数,用于查询电话号码相等且数据类型为电话的联系人信息。 + + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); +//方法将电话号码中的"+"替换为最小匹配的呼叫者ID。 + 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; + } + } +} + +```java +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; + + /** + * 以下是系统文件夹的标识符: + * {@link Notes#ID_ROOT_FOLDER} 是默认文件夹 + * {@link Notes#ID_TEMPARAY_FOLDER} 用于不属于任何文件夹的笔记 + * {@link Notes#ID_CALL_RECORD_FOLDER} 用于存储通话记录 + * {@link Notes#ID_TRASH_FOLDER} 用于垃圾文件夹 + */ + 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_FOLDER = -3; + + // Intent额外键 + 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_INVALID = -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; + } +} +```java +/** + * 用于查询所有笔记和文件夹的 Uri + */ +public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); + +/** + * 用于查询数据的 Uri + */ +public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + +public interface NoteColumns { + /** + * 行的唯一 ID + *

类型: INTEGER (long)

+ */ + public static final String ID = "_id"; + + /** + * 笔记或文件夹的父级 ID + *

类型: INTEGER (long)

+ */ + public static final String PARENT_ID = "parent_id"; + + /** + * 笔记或文件夹的创建日期 + *

类型: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; + + /** + * 最新修改日期 + *

类型: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + + + /** + * 提醒日期 + *

类型: INTEGER (long)

+ */ + public static final String ALERTED_DATE = "alert_date"; + + /** + * 文件夹名称或笔记的文本内容 + *

类型: TEXT

+ */ + public static final String SNIPPET = "snippet"; + + /** + * 笔记的小部件 ID + *

类型: INTEGER (long)

+ */ + public static final String WIDGET_ID = "widget_id"; + + /** + * 笔记的小部件类型 + *

类型: INTEGER (long)

+ */ + public static final String WIDGET_TYPE = "widget_type"; + + /** + * 笔记的背景颜色 ID + *

类型: INTEGER (long)

+ */ + public static final String BG_COLOR_ID = "bg_color_id"; + + /** + * 对于文本笔记,它没有附件,对于多媒体笔记,它至少有一个附件 + *

类型: INTEGER

+ */ + public static final String HAS_ATTACHMENT = "has_attachment"; + + /** + * 文件夹中笔记的计数 + *

类型: INTEGER (long)

+ */ + public static final String NOTES_COUNT = "notes_count"; +} + +```java +/** + * 用于表示文本笔记的静态内部类 + */ +public static final class TextNote implements DataColumns { + /** + * 指示文本是否处于待办事项模式的模式 + *

类型: Integer 1: 待办事项模式 0: 正常模式

+ */ + 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"; + + /** + * 用于查询文本笔记的 Uri + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); +} + +/** + * 用于表示通话记录的静态内部类 + */ +public static final class CallNote implements DataColumns { + /** + * 此记录的通话日期 + *

类型: INTEGER (long)

+ */ + public static final String CALL_DATE = DATA1; + + /** + * 此记录的电话号码 + *

类型: 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"; + + /** + * 用于查询通话记录的 Uri + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); +} +``` + + +package net.micode.notes.ui; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Dialoginterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings; +import android.view.Window; +import android.view.WindowManager; +import net.micode.notes.R: +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; +import java.io.IOException; + +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + private long mNoteld; + //文本在数据库存储中的ID号 + private String mSnippet; + //闹钟提示时出现的文本片段 + private static final intSNIPPET_PREW_MAX_LEN=60; + +MediaPlayer mPlayer; + +@Override +protected vold onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +//Bundle类型的数据与Map类型的数据相似,都是以key-value的形式存储数据的 +//onsavelnstanceState方法是用来保存Activity的状态的 +//能从 onCreate的参数savedInsanceState中获得状态数据 +requestWindowFeature(Window.FEATURE_NO_TITLE); +//界面显示一-无标题 +final Window win=getWindow(); +win.addFlags(WindowManager.LayoutParams.FLAG SHOW_WHEN LOCKED); +if (lisScreenOn()){ +win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON +//保持窗体点亮 +WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON +//将窗体点亮 +|WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON +//允许窗体点亮时锁屏 +WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);] +//在手机锁屏后如果到了闹钟提示时间,点亮屏幕 +Intent intent =getIntent(); +try{ +mNoteld=Long.valueOf(intent.getData().getPathSegments().get(1)); +mSnippet =DataUtils.getSnippetByld(this.getContentResolver(), mNoteld); +//根据ID从数据库中获取标签的内容: +//getContentResolver()是实现数据共享,实例存储。 +mSnippet = mSnippet.length() >SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, +SNIPPET_PREW_MAX_LEN)+getResources().getString(R.string.notelist_string_info):mSnippet; +//判断标签片段是否达到符合长度} catch (IllegalArgumentException e){ e.printStackTrace(); return; + +try +// 代码区 + +catch(Exception e) +//异常处理 +代码区如果有错误,就会返回所写异常的处理。*/ mPlayer = new MediaPlayer(); +if (DataUtils,visibleInNoteDatabase(getContentResolver(), mNoteld, Notes.TYPE_NOTE)) { showActionDialog(); +试访 试读 +//弹出对话框 playAlarmSound();//闹钟提示音激发}else{ finish(); +//完成闹钟动作 + +private boolean isScreenOn(){ +//判断屏幕是否锁屏,调用系统函数判断 +PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); + +private vold playAlarmSound() {//闹钟提示音激发 +Uri url RingtoneManager.getActualDefaultRingtoneUri(this, +RingtoneManager.TYPE_ALARM); +//调用系统的铃声管理URI,得到闹钟提示音 +int silentModeStreams =Settings.System.getInt(getContentResolver(), +Settings.System.MODE_RINGER_STREAMS_AFFECTED,0); +if ((silentModeStreams & (1<SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, +SNIPPET_PREW_MAX_LEN) +getResources().getString(R.string.notelist_string_info):mSnippet; +//判断标签片段是否达到符合长度} catch (IllegalArgumentException e){ e.printStackTrace(); return; +/* try +// 代码区 +catch(Exception e){ +//异常处理 +代码区如果有错误,就会返回所写异常的处理。*/ mPlayer = new MediaPlayer(); +if (DataUtils.visiblelnNoteDatabase(getContentResolver(), mNoteld, Notes.TYPE_NOTE)) { showActionDialog(); +//弹出对话框 playAlarmSound(); +//闹钟提示音激发 + +} else{ finish(); +//完成闹钟动作 + +private boolean isScreenOn(){ +//判断屏幕是否锁屏,调用系统函数判断,最后返回值是布尔类型 +PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); + +private void playAlarmSound() { +//闹钟提示音激发 +Uri url RingtoneManager.getActualDefaultRingtoneUri(this, +RingtoneManager.TYPE_ALARM); +//调用系统的铃声管理URI,得到闹钟提示音 +int silentModeStreams = Settings.System.getint(getContentResolver(), +Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); +if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { mPlayer.setAudioStreamType(silentModeStreams);}else{ +mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); +try{ +mPlayer.setDataSource(this, url); +//方法:setDataSource(Context context, Uri uri) +//解释:无返回值,设置多媒体数据来源[根据Uni] mPlayer.prepare(); +//准备同步 +mPlayer.setLooping(true);//设置是否循环播放 + +mPlayer.start(); +//开始播放 +} catch (IllegalArgumentException e) { +// TODO Auto-generated catch block e.printStackTrace(); + +// TODO Auto-generated catch block e.printStackTrace(); +}catch (IllegalStateException e){ +// TODO Auto-generated ctch block e.printStackTrace();} catch (IOException e) { +// TODO Auto-generated catch block e.printStackTrace(); + +private void showActionDialog(){ +AlertDialog,Builder dialog = new AlertDialog.Builder(this); +//AlertDialog的构造方法全部是Protected的 +//所以不能直接通过new一个AlertDialog来创建出一个AlertDialog. + +//要创建一个AlertDialog,就要用到AlertDialog,Builder 中的create()方法 +//如这里的 dialog 就是新建了一个AlertDialog dialog.setTitle(R.string.app_name); +//为对话框设置标题 +dialog.setMessage(mSnippet); +//为对话框设置内容 +dialog.setPositiveButton(R.string.notealert_ok, this); +//给对话框添加"Yes"按钮 if (isScreenOn()) { +dialog.setNegativeButton(R.string.notealert_enter, this);} +//对话框添加"No"按钮 +dialog.show().setOnDismissListener(this); + +public void onClick(DialogInterface dialog, int which) { switch (which) { +//用which 来选择 click后下一步的操作 case DialogInterface.BUTTON_NEGATIVE: +//这是取消操作 +Intent intent = new Intent(this, NoteEditActivity.class);//实现两个类间的数据传输 + +/* +*Get currentMinute +*@return The Current Minute + +public int getCurrentMinute(){ +return mDate.get(Calendar.MINUTE); +/** +*Set current minute +public void setCurrentMinute(int minute){ +if (!mlnitialising && minute ==getCurrentMinute()) { return; + +mMinuteSpinner.setValue(minute); + +mDate.set(Calendar.MINUTE,minute); onDateTimeChanged(); + +1** +*@return true lf this is in 24 hour view else false. +\ +public boolean is24HourView (){ return mls24HourView; + +/* +*Set whether in 24 hour or AM/PM mode. +*@param is24HourView True for 24 hour mode. False for AM/PM mode. +public void set24HourView(boolean is24HourView){ +// TODO Auto-generated catch block e.printStackTrace(); +//e.printStackTrace()函数功能是抛出异常,还将显示出更深的调用信息 +//System.out.printin(e),这个方法打印出异常,并且输出在哪里出现的异常 +}catch (SecurityException e) { +// TODO Auto-generated catch block +e.printStackTrace(); +} catch (IllegalStateException e) +{ +// TODO Auto-generated catch block e.printStackTrace();} catch (I0Exception e) + { +// TODO Auto-generated catch block e.printStackTrace(); + +private void showActionDialog(){ +AlertDialog.Builder dialog = new AlertDialog.Builder(this); +//AlertDialog的构造方法全部是Protected的 +//所以不能直接通过new一个AlertDialog来创建出一个AlertDialog。 +//要创建一个AlertDialog,就要用到AlertDialog.Builder中的create()方法 + +//为对话框设置标题 +dialog.setMessage(mSnippet); +//为对话框设置内容 +dialog.setPositiveButton(R.string.notealert_ok, this); +//给对话框添加"Yes"按钮 if (isScreenOn()) { +dialog.setNegativeButton(R.string.notealert_enter, this);} +//对话框添加"No"按钮 +dialog.show().setOnDismissListener(this); + +public void onClick(Dialoginterface dialog, int which) { +switch (which) { +//用which 来选择click后下一步的操作 case DialogInterface.BUTTON NEGATIVE: +//这是取消操作 +Intent intent = new Intent(this, NoteEditActivity.class); +//实现两个类间的数据传输 + + +if (mls24HourView== is24HourView) { return; +mls24HourView= is24HourView; +mAmPmSpinner.setVisibility(is24HourView?View.GONE:View.VISIBLE); +int hour = getCurrentHourOfDay(); +updateHourControl(); setCurrentHour(hour); +updateAmPmControl(); + +private void updateDateControl(){ +Calendar cal =Calendar.getlnstance(); +cal.setTimelnMillis(mDate.getTimelnMillis()); +cal.add(Calendar.DAY OF YEAR,-DAYS IN ALL WEEK/2-1); +mDateSpinner.setDisplayedValues(null); for (inti=0;iSNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, +SNIPPET_PREW_MAX_LEN)+getResources().getString(R.string.notelist_string_info):mSnippet; +//判断标签片段是否达到符合长度} catch (IllegalArgumentException e){ e.printStackTrace(); return; + +try +// 代码区 + +catch(Exception e) +//异常处理 +代码区如果有错误,就会返回所写异常的处理。*/ mPlayer = new MediaPlayer(); +if (DataUtils,visibleInNoteDatabase(getContentResolver(), mNoteld, Notes.TYPE_NOTE)) { showActionDialog(); +试访 试读 +//弹出对话框 playAlarmSound();//闹钟提示音激发}else{ finish(); +//完成闹钟动作 + +private boolean isScreenOn(){ +//判断屏幕是否锁屏,调用系统函数判断 +PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); + +private vold playAlarmSound() {//闹钟提示音激发 +Uri url RingtoneManager.getActualDefaultRingtoneUri(this, +RingtoneManager.TYPE_ALARM); +//调用系统的铃声管理URI,得到闹钟提示音 +int silentModeStreams =Settings.System.getInt(getContentResolver(), +Settings.System.MODE_RINGER_STREAMS_AFFECTED,0); +if ((silentModeStreams & (1<0" + ";" + + " END"; + +/** + * 当将新的笔记插入文件夹时增加文件夹的笔记计数 + */ +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"; + +/** + * 当从文件夹中删除笔记时减少文件夹的笔记计数 + */ +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"; + +/** + * 当插入类型为 {@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"; + +/** + * 当类型为 {@link DataConstants#NOTE} 的数据发生更改时更新笔记内容 + */ +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"; +```java +/** + * 当类型为 {@link DataConstants#NOTE} 的数据被删除时,更新笔记内容 + */ +private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = + "CREATE TRIGGER update_note_content_on_delete " + + "AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END"; + +/** + * 删除已删除笔记所属的数据 + */ +private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = + "CREATE TRIGGER delete_data_on_delete " + + "AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; + +/** + * 删除已删除文件夹所属的笔记 + */ +private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = + "CREATE TRIGGER folder_delete_notes_on_delete " + + "AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + +/** + * 将已移至回收站的文件夹中的笔记移动至回收站 + */ +private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = + "CREATE TRIGGER folder_move_notes_on_trash " + + "AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + +public NotesDatabaseHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); +} + +public void createNoteTable(SQLiteDatabase db) { + db.execSQL(CREATE_NOTE_TABLE_SQL); + reCreateNoteTableTriggers(db); + createSystemFolder(db); + Log.d(TAG, "note table has been created"); +} + +private void reCreateNoteTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); + + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); + db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); + db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); +} + +private void createSystemFolder(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + + /** + * 为通话笔记创建通话记录文件夹 + */ + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * 根文件夹,即默认文件夹 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * 临时文件夹,用于移动笔记 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * 创建回收站文件夹 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); +} + +public void createDataTable(SQLiteDatabase db) { + db.execSQL(CREATE_DATA_TABLE_SQL); + reCreateDataTableTriggers(db); + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); + Log.d(TAG, "data table has been created"); +} + +private void reCreateDataTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); + + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); +} + + +```java +/** + * 获取数据库辅助类的实例,使用了同步锁确保线程安全 + * @param context 上下文对象 + * @return NotesDatabaseHelper 实例 + */ +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; // 是否跳过版本2的升级 + + if (oldVersion == 1) { + upgradeToV2(db); // 升级到版本2 + skipV2 = true; // 此次升级包含了从版本2到版本3的升级 + oldVersion++; + } + + if (oldVersion == 2 && !skipV2) { + upgradeToV3(db); // 升级到版本3 + reCreateTriggers = true; + oldVersion++; + } + + if (oldVersion == 3) { + upgradeToV4(db); // 升级到版本4 + oldVersion++; + } + + // 如果需要重新创建触发器,则执行相应的方法 + if (reCreateTriggers) { + reCreateNoteTableTriggers(db); + reCreateDataTableTriggers(db); + } + + // 如果旧版本号与新版本号不匹配,则抛出异常 + if (oldVersion != newVersion) { + throw new IllegalStateException("升级笔记数据库到版本 " + newVersion + " 失败"); + } +} + +/** + * 升级到版本2的方法 + * @param db SQLiteDatabase 对象 + */ +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); // 重新创建数据表 +} + +/** + * 升级到版本3的方法 + * @param db SQLiteDatabase 对象 + */ +private void upgradeToV3(SQLiteDatabase db) { + // 删除不再使用的触发器 + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); + // 添加一个用于保存 Gtask ID 的列 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + + " TEXT NOT NULL DEFAULT ''"); + // 添加一个垃圾箱系统文件夹 + ContentValues values = new ContentValues(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); +} + +/** + * 升级到版本4的方法 + * @param db SQLiteDatabase 对象 + */ +private void upgradeToV4(SQLiteDatabase db) { + // 添加一个版本号的列 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + + " INTEGER NOT NULL DEFAULT 0"); +} + +