Compare commits

...

1 Commits
main ... master

Author SHA1 Message Date
zhushiying cec6c0fd9b v1
11 months ago

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

@ -0,0 +1,80 @@
/*
* 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。
//导入了多个 Android 相关的类用于处理上下文、数据库游标、联系人数据契约、电话号码工具以及日志记录等功能同时还导入了HashMap类用于缓存数据
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;
//sContactCache是一个静态的HashMap用于缓存电话号码和对应的联系人姓名通过以电话号码作为键联系人姓名作为值的方式存储方便后续快速查找避免重复查询数据库。
//TAG一个静态的常量字符串用于在日志输出中标识当前类方便在查看日志时区分不同类产生的日志信息。
//CALLER_ID_SELECTION是一个静态的常量字符串它构建了一个用于查询联系人数据库的SQL选择条件语句旨在查找与给定电话号码匹配的联系人信息涉及到比较电话号码是否相等、联系人数据的MIME类型是否为电话号码类型以及通过关联其他表来进一步筛选合适的联系人记录等条件。
public class Contact {
private static HashMap<String, String> 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 = '+')";
//首先方法接受一个Context对象用于访问系统资源和服务在这里用于查询联系人数据库和一个电话号码字符串作为参数。
//接着检查缓存HashMap是否为null如果是则初始化它确保缓存机制可用。然后判断缓存中是否已经存在给定电话号码对应的联系人姓名如果存在则直接返回缓存中的姓名避免再次查询数据库提高效率。
public static String getContact(Context context, String phoneNumber) {
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
//如果缓存中不存在对应的联系人姓名会根据给定电话号码动态构建查询的选择条件。这里调用PhoneNumberUtils.toCallerIDMinMatch方法来处理电话号码可能是为了符合数据库中电话号码匹配的某种格式要求等替换掉CALLER_ID_SELECTION中的+符号,形成最终合适的查询条件字符串。
//然后使用Context的getContentResolver方法获取内容解析器并通过它发起一个数据库查询操作查询的URI是Data.CONTENT_URI对应联系人数据的内容提供器URI指定只查询联系人的显示姓名通过Phone.DISPLAY_NAME字段传入构建好的选择条件以及电话号码参数最后一个null表示查询的排序规则为空即不指定特定排序方式
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);
//当查询返回的游标不为null且游标可以移动到第一条记录意味着找到了匹配的联系人记录尝试从游标中获取联系人的姓名这里假设查询结果的第一列就是联系人姓名通过getString(0)获取然后将电话号码和获取到的姓名存入缓存HashMap中方便下次查询使用最后返回该姓名。
//如果在获取游标中的字符串数据时出现越界异常比如查询结果的列数不符合预期等情况会记录错误日志使用Log.e输出错误级别日志并返回null同时无论是否出现异常都会在finally块中关闭游标释放资源。
//如果游标为null或者游标中没有可移动到的第一条记录即没有找到匹配的联系人则记录一条调试级别的日志使用Log.d说明没有找到与给定电话号码匹配的联系人然后返回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;
}
}
}

@ -0,0 +1,117 @@
/*
* 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.
*/
//该语句指定了 Node 类所属的包名,将其归类到 net.micode.notes.gtask.data 这个命名空间下,便于在项目中进行组织和管理代码模块,避免类名冲突等问题。
package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
//定义了一系列整型的公共静态常量,用于表示不同的同步操作类型,例如:
//public这是一个访问修饰符表示该常量具有公共访问权限意味着在同一个包内以及被导入该类所在包的其他类中都可以访问这个常量。
//static表明这个变量属于类级别而不是实例级别。也就是说不需要创建类的实例对象就可以通过类名直接访问该变量例如 Node.SYNC_ACTION_NONE假设所在类为 Node
//final意味着这个变量一旦被赋值就不能再被修改它是一个常量。常量通常用于表示那些在程序运行过程中固定不变的值这有助于提高代码的可读性和可维护性避免不小心修改了其值而导致程序出现逻辑错误。
//int定义了该常量的数据类型为整型它可以用来存储整数值在这里被赋值为 0。
//SYNC_ACTION_NONE表示没有同步操作发生可能用于初始化或者默认状态的标识。
//SYNC_ACTION_ADD_REMOTE意味着在远程端添加数据的同步操作。
//SYNC_ACTION_ADD_LOCAL对应在本地端添加数据的同步操作以此类推清晰地划分了各种可能的同步动作场景方便在整个同步逻辑处理过程中通过这些常量来区分不同情况。
public abstract class Node {
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
//声明了四个私有成员变量:
//mGid大概率是用于标识节点的全局唯一标识符从变量名推测类型为字符串用于区分不同的节点对象比如在数据同步过程中通过 Gid 来确定是哪个具体的任务或者数据项。
//mName字符串类型应该是用于存储节点的名称方便展示或者进行一些基于名称的查找、判断等操作。
//mLastModified长整型记录节点最后一次被修改的时间戳对于判断数据的新旧程度、是否需要更新等同步相关操作很有帮助。
//mDeleted布尔类型用于表示节点是否已经被删除在同步删除操作或者判断节点状态时可以用到这个属性。
private String mGid;
private String mName;
private long mLastModified;
private boolean mDeleted;
//这是 Node 类的默认构造函数,它将各个成员变量进行了初始化赋值,把 mGid 设置为 nullmName 初始化为空字符串mLastModified 设为 0时间戳初始值mDeleted 设为 false确保对象创建时各属性有合理的初始状态。
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
//getCreateAction接受一个整数类型的 actionId 参数,需要在子类中实现返回一个 JSONObject用于获取创建操作对应的相关数据可能是根据不同的创建场景比如远程创建或者本地创建等按照 actionId 来生成对应的创建操作描述信息,以 JSON 格式返回)。
//getUpdateAction同样以 actionId 为参数,返回 JSONObject用于获取更新操作相关的数据内容根据具体的更新场景生成相应的 JSON 格式描述信息。
//setContentByRemoteJSON接收一个 JSONObject 参数,在子类中实现时应该是根据远程传来的 JSON 数据来设置节点的内容,实现从远程数据到节点内部数据的转换和赋值操作。
//setContentByLocalJSON与上一个类似不过是根据本地的 JSON 数据来设置节点内容,将本地存储的相关 JSON 格式数据解析并赋值给节点的对应属性。
//getLocalJSONFromContent返回 JSONObject用于从节点当前的内容提取出对应的本地 JSON 格式数据,以便进行本地存储或者传输等操作。
//getSyncAction接受一个 Cursor 参数(可能是数据库游标,意味着可能要从数据库查询结果中获取信息),在子类中要实现根据游标所指向的数据来判断并返回当前节点对应的同步操作类型(通过前面定义的那些同步操作类型常量来表示)。
public abstract JSONObject getCreateAction(int actionId);
public abstract JSONObject getUpdateAction(int actionId);
public abstract void setContentByRemoteJSON(JSONObject js);
public abstract void setContentByLocalJSON(JSONObject js);
public abstract JSONObject getLocalJSONFromContent();
public abstract int getSyncAction(Cursor c);
//这些方法是标准的对私有成员变量的访问器和修改器方法遵循了面向对象编程中封装的原则使得外部类可以通过这些公共方法来获取get 方法和修改set 方法) Node 类内部的私有成员变量,保证了数据的安全性和对类内部状态的合理控制。
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) {
this.mName = name;
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -0,0 +1,57 @@
/*
* 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;
// NoteWidgetProvider_2x类继承自NoteWidgetProvider用于特定样式可能与2倍尺寸相关的桌面小部件相关功能实现
// 比如处理小部件的更新、获取对应布局资源、背景资源以及定义小部件类型等操作
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 重写onUpdate方法该方法会在桌面小部件需要更新时被调用例如配置改变、定时更新等情况
// 此处调用了父类的update方法通常父类的update方法包含了通用的小部件更新逻辑
// 子类可以在此基础上根据自身特点再做额外的定制化处理(当前代码暂时仅调用父类方法)
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
// 重写getLayoutId方法其目的是返回此桌面小部件对应的布局资源的ID
// 这里返回R.layout.widget_2x表示对应的布局文件是项目中名为widget_2x.xml的布局资源
// 该布局文件定义了小部件具体的界面展示样式,如组件构成及排列等
@Override
protected int getLayoutId() {
return R.layout.widget_2x;
}
// 重写getBgResourceId方法用于根据传入的背景资源相关的IDbgId参数来获取对应的小部件背景资源的实际ID
// 通过调用ResourceParser类中WidgetBgResources内部类假设的getWidget2xBgResource静态方法来获取相应的背景资源ID
// 意味着具体的背景资源获取逻辑被封装在该工具类及其内部类方法中会依据不同的bgId查找对应的背景资源
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
// 重写getWidgetType方法用来返回小部件的类型标识
// 当前返回的是Notes.TYPE_WIDGET_2X表明这个小部件属于一种特定的类型可能代表2倍尺寸之类与其他小部件区分的类型定义
// 方便在整个应用中根据小部件类型做不同处理,例如不同类型小部件的显示和交互逻辑会有所差异
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;
}
}

@ -0,0 +1,355 @@
/*
* 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;
// `Notes`类用于定义与笔记应用相关的各种常量、接口以及内部类,这些定义为整个笔记应用的数据存储、查询以及交互等操作提供了统一的规范和基础信息。
public class Notes {
// 定义内容提供器Content Provider的授权字符串用于标识该笔记应用在安卓系统中的唯一身份
// 在进行基于内容提供器的跨应用数据访问等操作时会用到这个授权标识来确定访问的数据源。
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;
// 定义用于传递提醒日期信息的Intent额外数据的键在不同组件如Activity、Service等之间通过Intent传递数据时
// 如果涉及到笔记的提醒日期相关信息传递,就可以使用这个键来确保数据的正确标识和获取。
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
// 定义用于传递笔记背景颜色标识符信息的Intent额外数据的键方便在组件间传递和获取笔记背景颜色相关设置信息。
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
// 定义用于传递笔记对应的桌面小部件Widget标识符信息的Intent额外数据的键用于关联笔记与对应的小部件相关操作和数据传递。
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
// 定义用于传递笔记对应的桌面小部件Widget类型信息的Intent额外数据的键例如不同尺寸或样式的小部件可以通过此键来区分类型信息传递。
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";
// 定义用于传递文件夹标识符信息的Intent额外数据的键在涉及文件夹相关操作如打开指定文件夹、移动笔记到指定文件夹等
// 通过此键可以准确传递文件夹的唯一标识信息,便于业务逻辑处理。
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
// 定义用于传递通话日期信息的Intent额外数据的键主要用于通话记录相关笔记中传递通话发生的日期信息便于展示、查询等操作。
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
// 定义表示无效的桌面小部件类型的常量,在判断小部件类型是否合法、进行相关类型筛选等操作时可以使用此常量作为参照。
public static final int TYPE_WIDGET_INVALIDE = -1;
// 定义一种桌面小部件类型的常量可能表示尺寸为2倍具体含义需结合应用实际情况比如相对标准尺寸的倍数等的小部件类型用于区分不同规格的小部件。
public static final int TYPE_WIDGET_2X = 0;
// 定义另一种桌面小部件类型的常量或许表示尺寸为4倍同样需结合应用具体情况确定含义的小部件类型方便对不同大小小部件进行管理和操作。
public static final int TYPE_WIDGET_4X = 1;
// `DataConstants`内部类,用于定义与笔记数据相关的一些常量,特别是涉及到不同类型笔记数据的标识,方便在数据处理、判断类型等操作中使用。
public static class DataConstants {
// 定义表示普通文本笔记类型的常量,其值来源于`TextNote.CONTENT_ITEM_TYPE`,用于在整个应用中统一标识文本笔记这种数据类型。
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
// 定义表示通话记录笔记类型的常量,其值来源于`CallNote.CONTENT_ITEM_TYPE`,用于明确区分通话记录相关的笔记数据类型。
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
/**
* Uri to query all notes and folders
*/
/**
* URIURI
* URI`AUTHORITY``/note`
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
*/
/**
* URI
* 使URIURI`/data`
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// `NoteColumns`接口,用于定义笔记表(数据库中存储笔记记录的表)中各个列的名称常量,使得在代码中对数据库列操作时可以使用统一的、语义明确的名称,提高代码可读性和可维护性。
public interface NoteColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示每一行(即每条笔记记录)唯一标识符的列名常量,数据类型为`INTEGER`(长整型),用于唯一标识每条笔记,便于数据库查询、更新、删除等操作。
public static final String ID = "_id";
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示笔记或文件夹的父级标识符的列名常量,数据类型为`INTEGER`(长整型),通过这个列可以构建笔记的层级关系,比如确定笔记所属的文件夹等。
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示笔记或文件夹创建日期的列名常量,数据类型为`INTEGER`(长整型),用于记录笔记或文件夹创建的时间戳信息,方便进行时间排序、筛选等操作。
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示笔记或文件夹最近一次修改日期的列名常量,数据类型为`INTEGER`(长整型),用于跟踪笔记数据的更新情况,比如判断是否有新的修改等操作。
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示提醒日期的列名常量,数据类型为`INTEGER`(长整型),用于设置和获取笔记的提醒时间相关信息,若笔记有提醒功能的话,此列存储相应提醒时间戳。
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
// 定义笔记表中表示文件夹名称或者文本笔记内容的列名常量,数据类型为`TEXT`,对于文件夹来说存储其名称,对于文本笔记则存储具体的文本内容。
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示笔记对应的桌面小部件标识符的列名常量,数据类型为`INTEGER`(长整型),用于关联笔记与特定的桌面小部件,方便进行相关交互操作。
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示笔记对应的桌面小部件类型的列名常量,数据类型为`INTEGER`(长整型),用于区分不同类型的桌面小部件,如前面定义的不同尺寸等类型的小部件
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示笔记背景颜色标识符的列名常量,数据类型为`INTEGER`(长整型),用于记录笔记所设置的背景颜色相关信息,便于显示等操作时获取使用。
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>
*/
// 定义笔记表中用于表示笔记是否有附件的列名常量,数据类型为`INTEGER`可以用布尔值0或1来表示有无附件情况方便判断笔记是否包含其他附属数据如图片、音频等附件
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示文件夹包含笔记数量的列名常量,数据类型为`INTEGER`(长整型),用于统计文件夹下的笔记个数,在文件夹相关操作(如移动笔记影响数量变化等)中会用到此列数据。
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
// 定义笔记表中表示文件类型(是笔记还是文件夹)的列名常量,数据类型为`INTEGER`,通过特定的值(如前面定义的`TYPE_NOTE`、`TYPE_FOLDER`等常量)来区分不同类型的记录。
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
// 定义笔记表中表示最后一次同步标识符的列名常量,数据类型为`INTEGER`(长整型),可能用于与云端或其他数据源进行数据同步相关操作时的标识和记录。
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
// 定义笔记表中用于指示本地是否有修改的标志列名常量,数据类型为`INTEGER`可以用布尔值0或1来表示是否被本地修改过便于数据同步等逻辑判断。
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
// 定义笔记表中表示笔记在移动到临时文件夹之前的原始父级标识符的列名常量,数据类型为`INTEGER`,用于在笔记移动等操作中记录和还原原来的层级关系等情况。
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
*/
// 定义笔记表中表示与Google Tasks相关的标识符的列名常量数据类型为`TEXT`可能用于与Google Tasks服务交互时标记笔记对应的任务等相关功能方便数据关联和操作。
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
// 定义笔记表中表示笔记版本代码的列名常量,数据类型为`INTEGER`(长整型),可用于记录笔记数据本身的版本情况,比如随着应用功能迭代,笔记数据结构有变化时,通过这个版本号可以方便地进行数据兼容性处理等操作。
public static final String VERSION = "version";
}
// `DataColumns`接口,用于定义笔记相关数据表格(存储笔记附属数据,如附件等数据的表)中各个列的名称常量,
// 与`NoteColumns`类似,是为了在代码中统一、清晰地操作数据表格中的列,方便进行数据的插入、查询、更新等操作。
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
// 定义数据表格中表示每一行(即每条数据记录)唯一标识符的列名常量,数据类型为`INTEGER`(长整型),用于唯一标识每条数据记录,便于数据库层面的各种操作。
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
// 定义数据表格中表示该行数据所代表的项目的MIME类型的列名常量数据类型为`TEXT`通过MIME类型可以明确数据的格式、性质等信息例如是文本、图片还是音频等类型的数据。
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
// 定义数据表格中表示该数据所属笔记的参考标识符的列名常量,数据类型为`INTEGER`(长整型),用于建立数据与对应笔记之间的关联关系,方便查询某个笔记相关的所有附属数据等操作。
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 定义数据表格中表示笔记或文件夹创建日期的列名常量,数据类型为`INTEGER`(长整型),与笔记表中的创建日期列对应,记录数据创建的时间戳信息,便于时间相关的排序、筛选等操作。
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 定义数据表格中表示笔记或文件夹最近一次修改日期的列名常量,数据类型为`INTEGER`(长整型),同样与笔记表中的修改日期列对应,用于跟踪数据的更新情况。
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*/
// 定义数据表格中表示数据内容的列名常量,数据类型为`TEXT`,用于存储具体的数据内容,比如对于文本类型的数据,这里存储实际的文本内容等。
public static final String CONTENT = "content";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 通用数据列,其含义根据`MIME_TYPE`数据的MIME类型而定用于存储整数类型的数据在不同类型数据的具体业务逻辑中可以赋予其不同的含义增加数据存储的灵活性。
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 通用数据列,同样根据`MIME_TYPE`确定含义,也是用于整数类型数据存储,可根据具体数据需求进行使用,方便扩展不同类型数据的额外整数信息存储。
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列,依据`MIME_TYPE`来明确其含义,用于存储文本类型的数据,为不同类型的数据提供了额外文本信息存储的字段,满足多样化的数据结构需求
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列,基于`MIME_TYPE`定义其意义,也是用于文本类型数据的存储,丰富了数据记录中可存储的文本相关信息内容。
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列,按照`MIME_TYPE`来确定其具体用途,同样用于文本类型数据的存储,进一步扩充了数据存储的文本信息容量,便于各种复杂数据结构的表示。
public static final String DATA5 = "data5";
}
// `TextNote`内部类,实现了`DataColumns`接口,表示文本笔记相关的数据结构和信息定义,可能用于专门处理文本笔记这种特定类型的数据操作和管理
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>
*/
// 定义用于表示文本笔记是否处于检查列表模式的列名(使用了通用数据列`DATA1`),数据类型为`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";
// 定义文本笔记的单项内容类型(同样用于内容提供器相关操作中准确标识数据类型),表示单个文本笔记记录的数据类型,与`CONTENT_TYPE`相对应,用于区分单个和多个笔记记录的情况
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 定义用于查询文本笔记数据的统一资源标识符URI遵循安卓内容提供器的URI构建规则通过这个URI可以专门对文本笔记相关的数据进行查询、操作等方便对文本笔记数据进行针对性管理。
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
// `CallNote`内部类,同样实现了`DataColumns`接口,是专门针对通话记录笔记的数据结构和信息定义,用于处理通话记录笔记相关的操作和管理,如查询、插入、更新等操作。
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
// 定义用于表示通话记录笔记的通话日期的列名(使用了通用数据列`DATA1`),数据类型为`INTEGER`(长整型),用于存储通话发生的时间戳信息,便于查询、展示通话记录的时间顺序等操作。
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*/
// 定义用于表示通话记录笔记的电话号码的列名(使用了通用数据列`DATA3`),数据类型为`TEXT`,用于存储通话对应的电话号码信息,方便查看、筛选等基于电话号码的操作。
public static final String PHONE_NUMBER = DATA3;
// 定义通话记录笔记的内容类型(用于安卓内容提供器中标识数据的类型,比如在查询、返回数据时确定返回的数据格式等情况),
// 这里表示通话记录笔记对应的目录类型的内容类型,格式遵循安卓内容提供器的规范,表明这是一个包含多个通话记录笔记记录的目录形式的数据类型。
// 例如,当通过内容提供器去查询获取一系列通话记录笔记时,服务端或数据源可以依据这个类型标识来返回符合要求的数据格式,客户端也能根据此类型来正确解析接收到的数据。
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
// 定义通话记录笔记的单项内容类型(同样用于内容提供器相关操作中准确标识数据类型),
// 表示单个通话记录笔记记录的数据类型,与`CONTENT_TYPE`相对应,用于区分单个和多个笔记记录的情况。
// 比如在获取某一条具体的通话记录笔记详细信息时,就会通过这个单项内容类型来准确表明要获取的数据是单个通话记录笔记实体,便于内容提供器进行精准的数据提供和交互操作。
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 定义用于查询通话记录笔记数据的统一资源标识符URI遵循安卓内容提供器的URI构建规则
// 通过这个URI可以专门对通话记录笔记相关的数据进行查询、操作等方便对通话记录笔记数据进行针对性管理。
// 当在代码中需要访问通话记录笔记相关的数据资源时,就可以使用这个`CONTENT_URI`来发起相应的查询请求或者其他数据库操作请求,确保准确地操作通话记录笔记相关的数据表等资源。
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -0,0 +1,545 @@
/*
* 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;
/**
* `NotesDatabaseHelper``SQLiteOpenHelper`
*
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 定义数据库的名称这里是“note.db”表示应用中存储笔记数据的数据库文件名
private static final String DB_NAME = "note.db";
// 定义数据库的版本号用于数据库升级等操作的版本控制这里初始版本号为4每次数据库结构有变更时可能会相应更新该版本号
private static final int DB_VERSION = 4;
/**
* `TABLE`便
*/
public interface TABLE {
// 定义笔记表的名称常量,用于在创建表、查询等操作中明确操作的是哪个表
public static final String NOTE = "note";
// 定义数据(可能是笔记附属的数据,如附件等相关数据)表的名称常量,同样用于相关数据库操作中对该表的引用
public static final String DATA = "data";
}
// 用于在日志输出中标识当前类的简单名称,方便在查看日志时快速定位到该类相关的操作记录,便于调试和问题排查
private static final String TAG = "NotesDatabaseHelper";
// 单例模式下的唯一实例对象,通过静态方法`getInstance`来获取该实例,保证整个应用只有一个该类实例来管理数据库操作,避免多次创建造成资源浪费或数据不一致等问题
private static NotesDatabaseHelper mInstance;
// 创建笔记表(`TABLE.NOTE`的SQL语句定义了表的各个列名、数据类型以及一些列的默认值等信息用于构建笔记表的结构
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" +
")";
// 创建数据(`TABLE.DATA`表的SQL语句规定了该表包含的列名、数据类型以及默认值等用于存储与笔记相关的数据信息比如可能是附件数据等
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 ''" +
")";
// 创建数据(`TABLE.DATA`)表中基于`NOTE_ID`字段的索引的SQL语句通过创建索引可以提高根据笔记ID查询相关数据的效率
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
*/
/**
* SQL`NoteColumns.PARENT_ID`
* `TABLE.NOTE``NoteColumns.PARENT_ID``NoteColumns.NOTES_COUNT`
*/
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
*/
/**
* SQL`NoteColumns.PARENT_ID`
* `TABLE.NOTE``NoteColumns.PARENT_ID`0`NoteColumns.NOTES_COUNT`
*/
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
*/
/**
* SQL`TABLE.NOTE`
* `NoteColumns.NOTES_COUNT``Parent_ID`
*/
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
*/
/**
* SQL`TABLE.NOTE`
* `NoteColumns.NOTES_COUNT``Parent_ID`0
*/
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}
*/
/**
* SQL`TABLE.DATA``DataConstants.NOTE`
* `TABLE.NOTE``NoteColumns.SNIPPET``CONTENT``NOTE_ID`
*/
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
*/
/**
* SQL`TABLE.DATA``DataConstants.NOTE`
* `TABLE.NOTE``NoteColumns.SNIPPET``CONTENT``NOTE_ID`
*/
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
*/
/**
* SQL`TABLE.DATA``DataConstants.NOTE`
* `TABLE.NOTE``NoteColumns.SNIPPET``NOTE_ID`
*/
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
*/
/**
* SQL`TABLE.NOTE``TABLE.DATA`
* `DataColumns.NOTE_ID``NoteColumns.ID`
*/
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
*/
/**
* SQL`TABLE.NOTE``TABLE.NOTE`
* `NoteColumns.PARENT_ID``NoteColumns.ID`
*/
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
*/
/**
* SQL`TABLE.NOTE``NoteColumns.PARENT_ID`
* `NoteColumns.PARENT_ID`
*/
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";
/**
* `SQLiteOpenHelper``null`
*
* @param context
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* `TABLE.NOTE``SQLiteDatabase`SQL
* `reCreateNoteTableTriggers`
* `createSystemFolder`
*
* @param db `SQLiteDatabase``getWritableDatabase``getReadableDatabase`
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
/**
* `TABLE.NOTE`
* 使`DROP TRIGGER IF EXISTS`
*
*
* @param db `SQLiteDatabase`SQL
*/
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除名为“increase_folder_count_on_update”的触发器如果存在的话该触发器用于在笔记移动到文件夹时增加文件夹的笔记数量
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
// 删除名为“decrease_folder_count_on_update”的触发器若存在此触发器在笔记从文件夹移出时减少文件夹的笔记数量
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
// 删除“decrease_folder_count_on_delete”触发器如果有用于在从文件夹删除笔记时减少文件夹笔记数量
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
// 删除“delete_data_on_delete”触发器若存在其作用是当笔记被删除时删除与之关联的数据表中的相关数据
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
// 删除“increase_folder_count_on_insert”触发器如果存在该触发器在向文件夹插入新笔记时增加文件夹笔记数量
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
// 删除“folder_delete_notes_on_delete”触发器若存在用于在文件夹被删除时删除该文件夹下的所有笔记
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
// 删除“folder_move_notes_on_trash”触发器如果有此触发器在文件夹被移动到回收站时将该文件夹下的笔记也移动到回收站
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 重新创建“increase_folder_count_on_update”触发器用于实现笔记移动到文件夹时文件夹笔记数量的增加逻辑
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
// 重新创建“decrease_folder_count_on_update”触发器以处理笔记从文件夹移出时文件夹笔记数量减少的情况
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
// 重新创建“decrease_folder_count_on_delete”触发器用于在从文件夹删除笔记时正确减少文件夹的笔记数量
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
// 重新创建“delete_data_on_delete”触发器确保在笔记被删除时能正确删除与之关联的数据
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
// 重新创建“increase_folder_count_on_insert”触发器实现向文件夹插入新笔记时增加文件夹笔记数量的功能
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
// 重新创建“folder_delete_notes_on_delete”触发器使得文件夹被删除时能相应删除其下的所有笔记
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
// 重新创建“folder_move_notes_on_trash”触发器保证文件夹移动到回收站时其下笔记也能正确移动到回收站
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
/**
*
* `TABLE.NOTE`使`ContentValues`
* `db.insert`
*
* @param db `SQLiteDatabase`
*/
private void createSystemFolder(SQLiteDatabase db) {
// 创建一个用于存储表列值的ContentValues对象用于后续设置要插入的文件夹记录的各列数据
ContentValues values = new ContentValues();
/**
* call record foler for call notes
*/
/**
* `TABLE.NOTE`
* ID`Notes.ID_CALL_RECORD_FOLDER``Notes.ID_CALL_RECORD_FOLDER`
* `Notes.TYPE_SYSTEM`
*/
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
*/
/**
* ID`Notes.ID_ROOT_FOLDER`
* `Notes.TYPE_SYSTEM`
*/
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
*/
/**
* ID`Notes.ID_TEMPARAY_FOLDER`
* `Notes.TYPE_SYSTEM`
*/
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
*/
/**
* ID`Notes.ID_TRASH_FOLER`
* `Notes.TYPE_SYSTEM`
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
* `TABLE.DATA``SQLiteDatabase`SQL
* `reCreateDataTableTriggers``NOTE_ID`SQL
* 便
*
* @param db `SQLiteDatabase`
*/
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");
}
/**
* `TABLE.DATA`
* 使`DROP TRIGGER IF EXISTS`
*
*
* @param db `SQLiteDatabase`SQL
*/
private void reCreateDataTableTriggers(SQLiteDatabase db) {
// 删除名为“update_note_content_on_insert”的触发器如果存在的话该触发器用于在向数据表格插入特定类型数据时更新关联笔记的内容
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
// 删除“update_note_content_on_update”触发器若存在此触发器在数据表格中的数据更新时相应更新关联笔记的内容
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
// 删除“update_note_content_on_delete”触发器如果有用于在数据表格中的数据被删除时对关联笔记内容进行相应处理
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
// 重新创建“update_note_content_on_insert”触发器以实现向数据表格插入特定类型数据时更新关联笔记内容的功能
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
// 重新创建“update_note_content_on_insert”触发器以实现向数据表格插入特定类型数据时更新关联笔记内容的功能
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
// 重新创建“update_note_content_on_delete”触发器使得在数据表格中的数据被删除时能按逻辑处理关联笔记的内容
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
/**
* `NotesDatabaseHelper`
*
* `mInstance``null``NotesDatabaseHelper`使
*
* @param context `NotesDatabaseHelper`访
* @return `NotesDatabaseHelper`
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
/**
* `SQLiteOpenHelper`
* `createNoteTable``createDataTable`
* 使
*
* @param db `SQLiteDatabase`SQL
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
/**
* `SQLiteOpenHelper`
* `oldVersion`134`NoteColumns.VERSION`
* `reCreateTriggers``newVersion`
*
*
* @param db `SQLiteDatabase`SQL
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 如果数据库的旧版本号为1表示是从版本1进行升级执行`upgradeToV2`方法进行升级操作,同时标记`skipV2`为`true`
// 意味着这次升级包含了从版本2到版本3的升级逻辑可能后续有相关判断依赖此标记然后将旧版本号加1表示已经处理了版本1的升级情况。
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
}
// 如果旧版本号为2且`skipV2`为`false`即不是从版本1直接升级过来包含了后续版本升级逻辑的情况则执行`upgradeToV3`方法进行版本3的升级操作
// 并将`reCreateTriggers`标记为`true`表示需要重新创建相关触发器之后将旧版本号再加1。
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
// 如果旧版本号为3则执行`upgradeToV4`方法将数据库升级到版本4然后将旧版本号加1
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 如果`reCreateTriggers`为`true`,即需要重新创建触发器的情况,分别调用`reCreateNoteTableTriggers`和`reCreateDataTableTriggers`方法
// 来重新创建笔记表和数据表相关的触发器,确保数据库升级后触发器的配置符合新的业务逻辑要求。
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 最后检查升级后的数据库版本号是否与期望的新版本号一致,如果不一致则抛出`IllegalStateException`异常,提示数据库升级失败,
// 并给出期望升级到的版本号信息,方便排查升级过程中出现的问题。
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
/**
* 12
* `TABLE.NOTE``TABLE.DATA`使`DROP TABLE IF EXISTS`
* `createNoteTable``createDataTable`12
*
* @param db `SQLiteDatabase`SQL
*/
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);
}
/**
* 23
* 使`DROP TRIGGER IF EXISTS`
* `TABLE.NOTE``NoteColumns.GTASK_ID`Google Tasks
* `ContentValues``db.insert`
*
* @param db `SQLiteDatabase`23
*/
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers
// 删除名为“update_note_modified_date_on_insert”的触发器如果存在的话可能该触发器在新版本中不再需要使用
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
// 删除名为“update_note_modified_date_on_delete”的触发器若存在
// 这个触发器或许是在删除笔记记录时对笔记修改日期进行相关处理的由于业务逻辑变更等原因在版本3中不再使用因此将其删除
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
// 删除名为“update_note_modified_date_on_update”的触发器如果有
// 同理,该触发器可能是在更新笔记记录时涉及笔记修改日期操作的,在新的版本里此功能不再需要,所以要删除掉这个触发器。
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id
// 向笔记表(`TABLE.NOTE`)添加一个新的列,名为`NoteColumns.GTASK_ID`用于存储与Google Tasks相关的标识符等信息。
// 数据类型定义为 `TEXT`(文本类型),并且设置该列的默认值为空字符串 `''`。
// 通过 `ALTER TABLE` 语句实现对表结构的修改添加新列以满足新的业务需求比如可能用于后续与Google Tasks服务交互时标记笔记对应的任务等功能。
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
// 创建一个 `ContentValues` 对象,用于存储要插入到数据库表中的数据(以键值对的形式)。
// 这里准备向笔记表(`TABLE.NOTE`)中插入一条新的系统文件夹记录,即回收站文件夹的记录信息。
ContentValues values = new ContentValues();
// 将回收站文件夹的唯一标识符(`Notes.ID_TRASH_FOLER`,应该是在别处定义好的常量)设置到 `NoteColumns.ID` 列中,
// 以此确定这个记录对应的文件夹的唯一标识,方便后续对该文件夹进行操作和识别
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
// 设置该文件夹的类型为 `Notes.TYPE_SYSTEM`,表明这是一个系统文件夹,用于和普通用户创建的文件夹进行区分,
// 系统文件夹通常具有特定的功能和管理方式,例如回收站文件夹有其特殊的处理逻辑(如还原、彻底删除等操作)
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
// 使用 `db.insert` 方法将包含上述设置好的列值的记录插入到笔记表(`TABLE.NOTE`)中。
// 第一个参数指定要插入数据的表名(这里就是 `TABLE.NOTE`),第二个参数通常用于指定如果插入的数据为空值时对应的处理策略(这里传 `null` 表示按默认方式处理),
// 第三个参数就是包含了要插入的具体数据的 `ContentValues` 对象。通过这个插入操作,就在数据库中创建了回收站文件夹的记录信息。
db.insert(TABLE.NOTE, null, values);
}
private void upgradeToV4(SQLiteDatabase db) {
// 使用 `ALTER TABLE` 语句来修改笔记表(`TABLE.NOTE`)的结构。
// 具体操作是添加一个新的列,列名为 `NoteColumns.VERSION`,这个列名通常在相关的列定义常量类(如 `Notes` 类中应该有对应定义)中有具体的定义和含义。
// 该列的数据类型被定义为 `INTEGER`(整数类型),并且设置其默认值为 `0`。添加这个版本列可能用于记录笔记数据本身的版本情况,
// 例如,随着应用功能迭代,笔记数据结构可能有变化,通过这个版本号可以方便地进行数据兼容性处理、区分不同阶段的笔记格式等操作,有助于维护数据的一致性和正确处理。
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -0,0 +1,403 @@
/*
* 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;
// `NotesProvider`类继承自`ContentProvider`,是安卓中用于在不同应用组件之间共享数据的重要组件,
// 它实现了基于内容提供器机制的各种数据操作方法,如查询、插入、删除、更新等,以对外提供笔记相关数据的访问接口。
public class NotesProvider extends ContentProvider {
// `UriMatcher`用于匹配传入的`Uri`,根据不同的`Uri`模式来确定要执行的具体操作逻辑,这里定义为静态成员变量,方便在整个类中进行`Uri`匹配判断。
private static final UriMatcher mMatcher;
// `NotesDatabaseHelper`对象用于辅助操作数据库,例如创建数据库连接、执行数据库相关的增删改查等操作,通过它可以获取可读写的数据库实例。
private NotesDatabaseHelper mHelper;
// 用于在日志输出中标识当前类的简单名称,方便在查看日志时快速定位到该类相关的操作记录,便于调试和问题排查。
private static final String TAG = "NotesProvider";
// 定义不同`Uri`模式对应的匹配码常量,用于在`UriMatcher`中区分不同的`Uri`路径和对应的操作,使得代码可以根据匹配码来执行相应的逻辑分支。
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;
// 静态代码块,用于初始化`UriMatcher`对象,向其中添加各种不同的`Uri`模式以及对应的匹配码,以便后续根据传入的`Uri`来准确判断其对应的操作类型。
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 添加匹配模式,当`Uri`的授权(`authority`)为`Notes.AUTHORITY`且路径为`"note"`时,匹配码为`URI_NOTE`,通常用于表示对所有笔记进行操作的情况。
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
// 当`Uri`的授权为`Notes.AUTHORITY`且路径为`"note/#"``#`表示数字,即具体某条笔记的标识符)时,匹配码为`URI_NOTE_ITEM`,用于对单条笔记进行操作的场景。
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
// 类似地,添加对数据(可能是笔记附属的数据,如附件等相关数据)表的`Uri`匹配模式,路径为`"data"`时匹配码为`URI_DATA`,表示操作所有相关数据的情况。
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
// 对于数据表里单条数据的`Uri`匹配模式,路径为`"data/#"`时匹配码为`URI_DATA_ITEM`,用于针对某一条具体数据记录进行操作。
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
// 添加用于搜索操作的`Uri`匹配模式,路径为`"search"`时匹配码为`URI_SEARCH`,用于处理一般性的搜索请求。
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
// 添加用于搜索建议(比如在搜索框输入内容时自动弹出的建议列表相关操作)的`Uri`匹配模式,路径为`SearchManager.SUGGEST_URI_PATH_QUERY`(这是安卓系统定义的标准搜索建议路径格式)时匹配码为`URI_SEARCH_SUGGEST`。
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
// 同样是用于搜索建议的`Uri`匹配模式,当路径为`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.
*/
/**
* SQLite`x'0A'``\n`
* `projection`
* `SearchManager`
*/
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;
// 定义搜索笔记摘要(`snippet`)内容的查询语句字符串,该语句基于`NOTES_SEARCH_PROJECTION`所定义的投影列,
// 从`TABLE.NOTE`(笔记表)中查询满足条件的数据,条件包括笔记摘要内容包含指定的搜索词(通过`LIKE`操作符实现模糊匹配)、
// 笔记不属于回收站文件夹(通过比较`NoteColumns.PARENT_ID`和`Notes.ID_TRASH_FOLER`来判断)以及笔记类型为普通笔记(通过比较`NoteColumns.TYPE`和`Notes.TYPE_NOTE`判断)。
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;
// `ContentProvider`的生命周期方法,在内容提供器创建时被调用,用于进行一些初始化操作。
// 这里通过`NotesDatabaseHelper`的单例模式获取其实例,以便后续使用该实例来操作数据库,若初始化成功则返回`true`表示内容提供器创建成功。
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
// 实现`ContentProvider`的查询方法,用于根据传入的`Uri`、查询投影(要返回的列)、查询条件、条件参数以及排序规则等信息,
// 从数据库中查询相应的数据,并返回一个`Cursor`对象,该对象可用于遍历查询结果集。
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
// 通过`NotesDatabaseHelper`获取一个可读的数据库实例,用于执行查询操作,确保不会对数据库进行写操作,保证数据的安全性(在只查询数据时)。
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
// 使用`UriMatcher`匹配传入的`Uri`,根据匹配结果执行不同的查询逻辑分支。
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 如果匹配到的是`URI_NOTE`模式,表示查询所有笔记,调用`db.query`方法执行查询操作,传入笔记表名、投影列、查询条件、条件参数、分组、过滤以及排序规则等参数,返回查询结果的`Cursor`对象。
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
// 当匹配到`URI_NOTE_ITEM`模式,意味着要查询单条笔记,先从`Uri`的路径段中获取笔记的标识符(通过`getPathSegments`方法获取路径段列表索引为1的元素就是笔记的ID
// 然后在查询条件中添加根据笔记ID进行匹配的条件通过拼接字符串构建完整的查询条件再执行查询操作并返回对应的`Cursor`对象。
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
// 对于`URI_DATA`模式,即查询所有数据(可能是笔记附属的数据)的情况,类似地调用`db.query`方法对数据表格进行查询操作,传入相应参数并返回结果`Cursor`对象。
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
// 当匹配到`URI_DATA_ITEM`模式,表示查询单条数据记录,同样先获取数据的标识符(从`Uri`路径段中获取然后构建包含数据ID的查询条件并执行查询返回对应的`Cursor`对象。
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:
// 如果匹配到的是搜索或搜索建议相关的`Uri`模式(`URI_SEARCH`或`URI_SEARCH_SUGGEST`),需要进行一些特殊的处理逻辑。
if (sortOrder!= null || projection!= null) {
// 如果传入了排序规则或者投影列参数(在搜索相关操作中不应该传入这些参数,有特定的处理方式),则抛出`IllegalArgumentException`异常,提示不应该指定这些参数。
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
String searchString = null;
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
// 如果是搜索建议的`Uri`模式且路径段数量大于1意味着有具体的搜索词部分在路径中则从路径段中获取搜索词内容。
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
// 如果是普通搜索的`Uri`模式,则从`Uri`的查询参数(通过`getQueryParameter`方法获取)中获取名为`"pattern"`的参数值作为搜索词内容。
searchString = uri.getQueryParameter("pattern");
}
if (TextUtils.isEmpty(searchString)) {
// 如果获取到的搜索词为空(即没有提供有效的搜索内容),则直接返回`null`,表示没有查询结果。
return null;
}
try {
// 对搜索词进行格式化处理,在前后添加`%`符号用于构建模糊匹配的查询条件在SQL中`LIKE '%搜索词%'`表示包含该搜索词的模糊匹配),然后使用格式化后的搜索词作为参数执行原始查询(`rawQuery`)操作,返回查询结果的`Cursor`对象。
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:
// 如果`Uri`匹配不到任何已知的模式,则抛出`IllegalArgumentException`异常,提示传入的`Uri`是未知的、不合法的。
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (c!= null) {
// 如果查询结果`Cursor`不为`null`,则设置通知`Uri`,当对应的数据发生变化时,可以通过内容提供器的通知机制通知相关的观察者(如使用该内容提供器数据的组件)数据已更新,这里使用传入的`Uri`作为通知的标识。
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
// 实现`ContentProvider`的插入方法,用于根据传入的`Uri`和要插入的数据(以`ContentValues`对象表示),
// 将数据插入到对应的数据库表中,并返回插入数据后生成的新记录的`Uri`(包含新记录的标识符),同时会发送数据变更通知给相关的观察者。
@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:
// 如果匹配到的是`URI_NOTE`模式,表示要向笔记表插入一条新笔记记录,调用`db.insert`方法将数据插入到笔记表中,返回插入后新笔记的标识符,同时将该标识符赋值给`insertedId`和`noteId`变量,方便后续使用
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
// 对于`URI_DATA`模式,即要向数据(笔记附属数据)表插入数据的情况,先检查插入的数据中是否包含`DataColumns.NOTE_ID`(用于关联数据所属的笔记),
// 如果包含则获取对应的笔记标识符赋值给`noteId`变量,然后调用`db.insert`方法将数据插入到数据表中,返回插入后新数据记录的标识符,赋值给`insertedId`和`dataId`变量。
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:
// 如果`Uri`不匹配已知的插入模式,则抛出`IllegalArgumentException`异常,提示传入的`Uri`是未知的、不合法的插入操作`Uri`。
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
// 如果成功插入了笔记记录(`noteId`大于0则通过内容提供器的`ContentResolver`发送数据变更通知,通知的`Uri`为笔记表对应的`ContentUris.withAppendedId`生成的`Uri`(包含了新插入笔记的标识符),告知相关观察者笔记表数据有更新。
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
// 如果成功插入了数据记录(`dataId`大于0同样通过`ContentResolver`发送数据变更通知,通知的`Uri`为数据(笔记附属数据)表对应的`ContentUris.withAppendedId`生成的`Uri`(包含了新插入数据的标识符),表示数据表数据有更新。
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回插入数据后生成的新记录的`Uri`,通过`ContentUris.withAppendedId`方法将插入记录的标识符添加到传入的`Uri`基础上,形成完整的新记录`Uri`,方便外部获取和使用新插入数据的标识信息。
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;
}
// 实现`ContentProvider`的删除方法,用于根据传入的`Uri`、删除条件以及条件参数,从对应的数据库表中删除满足条件的数据记录,
// 并返回删除的记录行数,同时在删除操作成功且满足一定条件时发送数据变更通知给相关观察者。
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
// 通过`NotesDatabaseHelper`获取一个可写的数据库实例,因为要执行删除操作,需要对数据库进行写操作权限,该实例将用于后续执行实际的删除操作。
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
// 使用`UriMatcher`匹配传入的`Uri`,根据匹配结果执行不同的删除逻辑分支。
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 如果匹配到的是`URI_NOTE`模式,表示要从笔记表中删除满足条件的多条笔记记录。
// 这里对`selection`条件进行补充,添加一个额外条件要求笔记的`ID`大于0可能是排除一些特殊的系统默认记录或者不符合删除条件的记录等情况
// 然后调用`db.delete`方法执行删除操作,传入笔记表名、完整的删除条件以及条件参数,将返回的删除行数赋值给`count`变量。
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 当匹配到`URI_NOTE_ITEM`模式,意味着要删除单条笔记记录,先从`Uri`的路径段中获取笔记的标识符(通过`getPathSegments`方法获取路径段列表索引为1的元素就是笔记的ID
// 接着进行一个判断,如果获取到的笔记`ID`小于等于0根据代码注释小于等于0的`ID`可能表示系统文件夹等不允许删除的情况),则直接跳出本次删除操作(不执行实际删除)。
// 否则,构建包含笔记`ID`的完整删除条件(通过拼接字符串,并调用`parseSelection`方法处理可能存在的额外条件部分),然后调用`db.delete`方法执行删除操作,将返回的删除行数赋值给`count`变量。
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:
// 对于`URI_DATA`模式,即要从数据(笔记附属数据)表中删除满足条件的多条数据记录的情况,直接调用`db.delete`方法执行删除操作,传入数据表名、删除条件以及条件参数,将返回的删除行数赋值给`count`变量,
// 同时将`deleteData`标记为`true`,表示此次操作涉及到了数据(附属数据)表的删除,后续可能会根据这个标记来发送相应的数据变更通知。
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
// 当匹配到`URI_DATA_ITEM`模式,表示要删除数据(附属数据)表中的单条数据记录,同样先从`Uri`路径段获取数据的标识符,然后构建包含数据`ID`的完整删除条件,
// 调用`db.delete`方法执行删除操作,将返回的删除行数赋值给`count`变量,并将`deleteData`标记为`true`,用于后续的数据变更通知判断。
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
// 如果`Uri`匹配不到任何已知的模式,则抛出`IllegalArgumentException`异常,提示传入的`Uri`是未知的、不合法的删除操作`Uri`。
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
// 如果成功删除的记录行数大于0表示删除操作有实际的数据被删除此时需要发送数据变更通知给相关的观察者例如使用该内容提供器数据的组件等
if (updateData) {
// 如果`deleteData`为`true`,表示此次删除操作涉及到了数据(附属数据)表,那么除了发送当前操作对应的`Uri`变更通知外,还需要发送笔记表对应的变更通知,
// 因为数据的删除可能会影响到与之关联的笔记相关的展示等情况,通过`ContentResolver`发送通知,通知的`Uri`为笔记表对应的统一资源标识符(`Notes.CONTENT_NOTE_URI`),告知相关观察者笔记表数据有更新
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 无论是否涉及数据(附属数据)表的删除,都要发送当前操作对应的`Uri`变更通知,通过`ContentResolver`通知相关观察者传入的`uri`所对应的资源数据有更新情况。
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
/**
* `selection`
* SQL `WHERE` SQL
*
* @param selection SQL `WHERE` `"name = 'John'"`
* @return `selection` `AND` `AND (name = 'John')`便 `selection`
*/
private String parseSelection(String selection) {
// 通过 `TextUtils.isEmpty(selection)` 判断传入的条件字符串是否为空。
// 如果不为空就按照SQL语句的语法规则添加 `AND` 关键字以及括号将原条件字符串包裹起来,形成一个可以在后续拼接中作为完整 `WHERE` 子句一部分的格式,然后返回该处理后的字符串;
// 如果为空字符串,则直接返回空字符串,避免后续拼接出现语法错误。
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
/**
* `NoteColumns.VERSION`
* `ID``selection``selectionArgs` `UPDATE`
* `TABLE.NOTE`1便
*
* @param id 0 `ID` -1 `selection`
* @param selection SQL `WHERE` `"category = 'work'"`
* @param selectionArgs `selection` `?`SQL使
*/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
// 开始构建 `UPDATE` 语句,首先指定要更新的表为笔记表(`TABLE.NOTE`),添加 `UPDATE` 关键字及表名到 `sql` 字符串构建器中。
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
// 添加 `SET` 关键字,用于在 `UPDATE` 语句中指定要更新的列以及其新的值,这里设置笔记的版本号列(`NoteColumns.VERSION`的值为原来的值加1即实现版本号自增的操作逻辑。
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
// 判断是否需要添加 `WHERE` 子句来限定更新操作的范围。如果传入的笔记 `ID` 大于0或者传入的条件字符串 `selection` 不为空,就表示需要添加 `WHERE` 子句,只对符合特定条件的笔记记录进行版本号更新操作,此时向 `sql` 字符串构建器中添加 `WHERE` 关键字。
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
// 如果传入的笔记 `ID` 大于0就在 `WHERE` 子句中添加根据笔记 `ID` 进行筛选的条件,将笔记表中的 `ID` 列与传入的具体 `ID` 值进行匹配,确保只针对特定 `ID` 的笔记记录进行版本号更新操作,把该条件添加到 `sql` 字符串构建器中。
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
// 如果传入的条件字符串 `selection` 不为空,需要对其进行进一步处理,使其可以正确地作为 `WHERE` 子句的一部分参与条件判断。
// 首先,根据笔记 `ID` 是否大于0来决定是否调用 `parseSelection` 方法处理 `selection`(如果 `id` 大于0则调用该方法对 `selection` 进行包裹处理,否则直接使用 `selection` 本身),得到处理后的条件字符串 `selectString`。
// 然后,遍历 `selectionArgs` 数组,将 `selectString` 中的占位符(通常是 `?`)依次替换为数组中的实际参数值(通过 `replaceFirst` 方法逐个替换占位符),使得条件语句能根据实际传入的参数准确筛选数据记录。
// 最后,将处理好的完整条件字符串添加到 `sql` 字符串构建器中的 `WHERE` 子句部分,完成整个 `UPDATE` 语句的构建。
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
sql.append(selectString);
}
// 通过 `NotesDatabaseHelper` 获取可写的数据库实例,然后使用该实例执行构建好的 `UPDATE` 语句实际对数据库中符合条件的笔记记录的版本号进行加1操作。
mHelper.getWritableDatabase().execSQL(sql.toString());
}
/**
* `ContentProvider` `getType` `Uri` MIME
* MIME
* `null` `Uri` MIME
*
* @param uri MIMEURI `uri` `uri` MIME
* @return `null`MIME `text/plain``vnd.android.cursor.dir/note`
*/
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
}
Loading…
Cancel
Save