Compare commits

...

9 Commits

@ -0,0 +1,95 @@
/*
* 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.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;
// Contact类用于处理与联系人相关的操作主要功能是根据电话号码查找对应的联系人姓名并利用缓存机制提高查找效率
public class Contact {
// 用于缓存电话号码与对应的联系人姓名,避免重复查询联系人数据库,提高性能
private static HashMap<String, String> sContactCache;
// 用于在日志输出时标识当前类,方便调试查看相关日志信息
private static final String TAG = "Contact";
// 构建的一个SQL查询条件字符串用于从联系人数据库中筛选出与给定电话号码匹配的联系人记录
// 条件包括电话号码相等判断、数据类型匹配确保是电话号码类型的数据以及原始联系人ID相关的筛选条件等
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和电话号码phoneNumber尝试查找对应的联系人姓名
public static String getContact(Context context, String phoneNumber) {
// 如果缓存为空首次使用或者缓存被清空等情况则创建一个新的HashMap用于缓存联系人信息
if (sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 首先检查缓存中是否已经存在该电话号码对应的联系人姓名,如果存在则直接返回缓存中的姓名,避免再次查询数据库
if (sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
// 根据给定的电话号码,替换查询条件中的占位符,生成最终用于查询数据库的选择条件字符串
// 这里使用PhoneNumberUtils的方法将电话号码转换为合适的格式用于匹配查询例如处理号码格式等情况
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 通过上下文的ContentResolver发起一个数据库查询操作
// 查询的是联系人数据的统一资源标识符CONTENT_URI
// 只查询联系人的显示名称Phone.DISPLAY_NAME这一列数据
// 使用前面生成的选择条件selection并传入要查询匹配的电话号码作为参数
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String[]{Phone.DISPLAY_NAME},
selection,
new String[]{phoneNumber},
null);
// 如果游标不为空(表示查询到了数据)且游标可以移动到第一条记录(意味着有匹配的联系人记录)
if (cursor!= null && cursor.moveToFirst()) {
try {
// 从游标中获取第一列索引为0的数据即联系人的显示名称
String name = cursor.getString(0);
// 将获取到的电话号码和对应的联系人姓名存入缓存中,方便下次查询使用
sContactCache.put(phoneNumber, name);
// 返回查找到的联系人姓名
return name;
} catch (IndexOutOfBoundsException e) {
// 如果在获取游标数据时出现越界异常例如查询结果的列数不符合预期等情况记录错误日志并返回null
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
// 无论是否成功获取到联系人姓名,都需要关闭游标,释放相关资源
cursor.close();
}
} else {
// 如果没有查询到匹配的联系人记录记录一条调试日志表明没有找到与给定电话号码匹配的联系人然后返回null
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

@ -0,0 +1,170 @@
/*
* 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.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
// GTaskASyncTask类继承自AsyncTask用于在后台执行与GTask相关的任务并根据任务进度和结果进行相应的界面反馈如显示通知等同时支持任务取消等操作
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义一个静态的整数作为GTask同步相关通知在系统中的唯一标识符用于区分不同的通知
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义一个接口,用于定义任务完成时需要执行的回调方法,外部类实现此接口来处理任务完成后的逻辑
public interface OnCompleteListener {
void onComplete();
}
// 用于保存应用的上下文环境,方便后续获取系统服务、资源等操作
private Context mContext;
// 用于管理和显示通知的系统服务对象,通过它可以创建、更新和取消通知
private NotificationManager mNotifiManager;
// GTask管理类的实例负责具体的GTask相关业务逻辑比如执行同步操作等
private GTaskManager mTaskManager;
// 实现了OnCompleteListener接口的对象引用用于在任务完成后进行回调通知
private OnCompleteListener mOnCompleteListener;
// 构造函数用于创建GTaskASyncTask实例传入上下文和任务完成监听器对象
public GTaskASyncTask(Context context, OnCompleteListener listener) {
// 将传入的上下文赋值给成员变量,方便后续使用
mContext = context;
// 保存传入的任务完成监听器对象
mOnCompleteListener = listener;
// 获取系统的通知管理服务,以便后续可以发送通知
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
// 获取GTaskManager的单例实例用于执行具体的GTask相关任务操作
mTaskManager = GTaskManager.getInstance();
}
// 用于取消正在进行的GTask同步任务通过调用GTaskManager中的取消同步方法来实现
public void cancelSync() {
mTaskManager.cancelSync();
}
// 对外提供的方法用于发布任务执行的进度信息实际上是调用AsyncTask的publishProgress方法来通知主线程更新进度显示
public void publishProgess(String message) {
// 创建一个包含要发布消息的字符串数组并调用publishProgress方法传递该数组
publishProgress(new String[] {
message
});
}
// 私有方法用于创建并显示通知根据传入的提示文本ID和具体内容来构建通知并发送到通知栏
private void showNotification(int tickerId, String content) {
PendingIntent pendingIntent;
// 根据传入的提示文本ID判断如果不是表示成功的提示文本ID通常对应不同的后续操作意图
if (tickerId!= R.string.ticker_success) {
// 创建一个PendingIntent用于当用户点击通知时启动NotesPreferenceActivity设置为不可变遵循Android系统的一些安全和兼容性要求
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
// 如果是表示成功的提示文本ID则创建一个PendingIntent用于当用户点击通知时启动NotesListActivity同样设置为不可变
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
}
// 使用Notification.Builder来构建通知对象设置通知的一系列属性
Notification.Builder builder = new Notification.Builder(mContext)
// 设置通知点击后自动取消
.setAutoCancel(true)
// 设置通知的标题,从资源文件中获取应用名称作为标题
.setContentTitle(mContext.getString(R.string.app_name))
// 设置通知的内容文本,传入具体的内容字符串
.setContentText(content)
// 设置通知的点击意图即用户点击通知后要执行的操作由前面创建的PendingIntent决定
.setContentIntent(pendingIntent)
// 设置通知触发的时间,使用当前系统时间
.setWhen(System.currentTimeMillis())
// 设置通知为持续进行状态(比如在同步过程中一直显示,直到任务完成等情况)
.setOngoing(true);
// 获取构建好的通知对象在不同的Android版本中获取方式可能有所不同这里是一种常见的方式
Notification notification = builder.getNotification();
// 通过通知管理器发送通知使用之前定义的通知ID来标识该通知确保唯一性
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
// 在后台线程中执行的方法用于执行实际的GTask同步任务相关逻辑同时发布初始的进度信息
@Override
protected Integer doInBackground(Void... unused) {
// 发布同步登录进度信息从资源文件中获取格式化后的字符串其中包含同步账号名称通过NotesPreferenceActivity获取
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
// 调用GTaskManager的sync方法执行同步操作并返回同步结果通常是表示不同状态的整数
return mTaskManager.sync(mContext, this);
}
// 在主线程中执行的方法,用于处理任务执行过程中的进度更新情况,显示相应的通知,并在特定条件下发送广播通知相关组件
@Override
protected void onProgressUpdate(String... progress) {
// 根据进度信息显示相应的通知使用表示正在同步的提示文本ID和传入的具体进度消息内容
showNotification(R.string.ticker_syncing, progress[0]);
// 如果当前上下文对象是GTaskSyncService类型可能是在特定的服务环境中运行该任务
if (mContext instanceof GTaskSyncService) {
// 则通过该服务发送广播,广播内容为进度消息,用于通知其他相关组件任务进度情况
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
// 在主线程中执行的方法,用于处理任务执行完成后的逻辑,根据不同的任务结果显示相应的通知,并在有任务完成监听器的情况下进行回调通知
@Override
protected void onPostExecute(Integer result) {
// 如果任务结果表示成功通常对应GTaskManager中定义的表示成功的状态常量
if (result == GTaskManager.STATE_SUCCESS) {
// 显示表示成功的通知通知内容包含成功同步的账号信息从资源文件中获取格式化后的字符串包含账号信息通过GTaskManager获取账号
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
// 在NotesPreferenceActivity中记录最后同步时间传入当前系统时间
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
// 如果任务结果表示网络错误,显示表示失败的通知,通知内容为从资源文件中获取的网络错误提示信息
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
// 如果任务结果表示内部错误,显示表示失败的通知,通知内容为从资源文件中获取的内部错误提示信息
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
// 如果任务结果表示同步被取消,显示表示取消的通知,通知内容为从资源文件中获取的同步取消提示信息
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
// 如果存在任务完成监听器对象即外部传入了实现了OnCompleteListener接口的对象
if (mOnCompleteListener!= null) {
// 创建一个新线程,用于执行任务完成的回调方法,避免阻塞主线程等情况
new Thread(new Runnable() {
public void run() {
// 调用任务完成监听器的onComplete方法通知外部任务已完成由外部实现具体的后续逻辑
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -0,0 +1,172 @@
/*
* 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.gtask.remote;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
// GTaskSyncService类继承自Service用于管理GTask相关的同步任务提供了启动、取消同步任务的功能并且能在不同状态下如内存不足等进行相应处理同时可以对外广播同步相关的状态信息
public class GTaskSyncService extends Service {
// 定义一个字符串常量用于作为区分不同同步操作行为的Intent的Extra字段的键名
public final static String ACTION_STRING_NAME = "sync_action_type";
// 定义一个整数常量表示启动同步任务的操作类型作为通过Intent传递操作类型时的一个标识值
public final static int ACTION_START_SYNC = 0;
// 定义一个整数常量,表示取消同步任务的操作类型
public final static int ACTION_CANCEL_SYNC = 1;
// 定义一个整数常量,表示无效的操作类型,可能用于默认情况或者错误处理时的标识
public final static int ACTION_INVALID = 2;
// 定义一个字符串常量作为用于广播GTask同步服务相关信息的Intent的动作Action名称用于区分不同来源的广播
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
// 定义一个字符串常量作为广播Intent中用于表示是否正在同步的Extra字段的键名用于传递同步状态信息
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
// 定义一个字符串常量作为广播Intent中用于表示同步进度消息的Extra字段的键名用于传递同步过程中的进度提示等信息
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
// 静态变量用于保存当前正在执行的GTaskASyncTask实例初始化为null表示没有正在进行的同步任务
private static GTaskASyncTask mSyncTask = null;
// 静态变量,用于保存同步任务的进度消息,初始化为空字符串
private static String mSyncProgress = "";
// 私有方法用于启动同步任务如果当前没有正在执行的同步任务则创建一个新的GTaskASyncTask实例并执行它
private void startSync() {
// 判断当前是否没有正在执行的同步任务即mSyncTask为null
if (mSyncTask == null) {
// 创建一个新的GTaskASyncTask实例传入当前服务的上下文this表示当前Service实例以及一个实现了OnCompleteListener接口的匿名内部类对象
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
// 实现OnCompleteListener接口的onComplete方法当同步任务完成时会被调用
public void onComplete() {
// 将正在执行的同步任务实例置为null表示同步任务已结束
mSyncTask = null;
// 发送一个空消息的广播(可能用于通知其他组件同步已完成等情况,具体作用依赖接收广播的组件逻辑)
sendBroadcast("");
// 停止当前服务自身,因为同步任务已经完成,服务不再需要继续运行
stopSelf();
}
});
// 发送一个空消息的广播(可能在任务启动阶段通知其他组件同步即将开始等情况)
sendBroadcast("");
// 执行同步任务启动后台线程开始执行GTask相关的同步操作
mSyncTask.execute();
}
}
// 私有方法用于取消正在执行的同步任务如果当前有正在执行的同步任务则调用其cancelSync方法进行取消操作
private void cancelSync() {
if (mSyncTask!= null) {
mSyncTask.cancelSync();
}
}
// 重写Service的onCreate方法在服务创建时被调用这里将正在执行的同步任务实例置为null进行初始化操作
@Override
public void onCreate() {
mSyncTask = null;
}
// 重写Service的onStartCommand方法当服务接收到通过startService方法启动的Intent时被调用根据传入Intent中的操作类型执行相应的同步操作逻辑
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 获取传入Intent中的额外数据以Bundle形式存储
Bundle bundle = intent.getExtras();
// 判断Bundle是否不为null且包含了用于区分操作类型的键ACTION_STRING_NAME
if (bundle!= null && bundle.containsKey(ACTION_STRING_NAME)) {
// 根据操作类型的值进行不同的处理通过获取操作类型整数值默认值为ACTION_INVALID如果键不存在
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
// 如果操作类型是启动同步任务
case ACTION_START_SYNC:
// 调用startSync方法启动同步任务
startSync();
break;
// 如果操作类型是取消同步任务
case ACTION_CANCEL_SYNC:
// 调用cancelSync方法取消同步任务
cancelSync();
break;
default:
break;
}
// 返回START_STICKY表示服务在被系统强制关闭后如内存不足等情况会尝试重新创建并启动保持服务的持续可用性但不会重新传递之前的Intent
return START_STICKY;
}
// 如果不符合上述条件调用父类的onStartCommand方法进行默认处理
return super.onStartCommand(intent, flags, startId);
}
// 重写Service的onLowMemory方法当系统内存不足时被调用在这里如果有正在执行的同步任务则取消该任务释放内存资源
@Override
public void onLowMemory() {
if (mSyncTask!= null) {
mSyncTask.cancelSync();
}
}
// 重写Service的onBind方法用于处理绑定服务的情况这里返回null表示该服务不支持绑定操作
public IBinder onBind(Intent intent) {
return null;
}
// 公共方法用于发送包含同步相关状态信息是否正在同步以及进度消息的广播更新同步进度消息并构建广播Intent发送出去
public void sendBroadcast(String msg) {
// 更新同步进度消息
mSyncProgress = msg;
// 创建一个新的Intent设置其动作Action为之前定义的GTask同步服务广播的动作名称用于标识该广播的来源和用途
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
// 向广播Intent中添加Extra数据设置是否正在同步的状态通过判断mSyncTask是否为null来确定不为null表示正在同步
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask!= null);
// 向广播Intent中添加Extra数据设置同步进度消息使用传入的msg参数作为消息内容
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
// 发送广播将包含同步相关信息的Intent广播出去以便其他组件可以接收并处理这些信息
sendBroadcast(intent);
}
// 静态公共方法用于从Activity中启动同步任务设置相关的Activity上下文到GTaskManager并发送启动同步任务的Intent到GTaskSyncService
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
// 静态公共方法用于从给定的上下文通常是Activity或其他组件所在的上下文中取消同步任务发送取消同步任务的Intent到GTaskSyncService
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
// 静态公共方法用于判断当前是否有正在执行的同步任务通过检查mSyncTask是否为null来确定返回布尔值表示同步状态
public static boolean isSyncing() {
return mSyncTask!= null;
}
// 静态公共方法,用于获取当前的同步进度消息,返回保存同步进度消息的字符串变量的值
public static String getProgressString() {
return mSyncProgress;
}
}

@ -0,0 +1,292 @@
/*
* 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类用于定义与笔记Notes相关的各种常量、接口以及内部类为整个笔记应用的数据结构和操作提供统一的规范和标识
public class Notes {
// 定义笔记应用的授权Authority字符串用于在内容提供器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;
/**
* ID
* {@link Notes#ID_ROOT_FOLDER }
* {@link Notes#ID_TEMPARAY_FOLDER }
* {@link Notes#ID_CALL_RECORD_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_FOLER = -3;
// 定义一系列用于传递额外数据的Intent的键Key常量方便在不同组件间传递与笔记相关的特定数据
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;
// DataConstants内部类用于定义笔记相关的数据类型常量目前主要涉及文本笔记TextNote和通话记录笔记CallNote的内容类型
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
/**
* UriAUTHORITY"/note"
* 使Uri访
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* UriAUTHORITY"/data"
*
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// NoteColumns接口定义了笔记表用于存储笔记相关信息的数据库表中各列的列名常量方便在数据库操作等代码中统一引用
public interface NoteColumns {
/**
* IDINTEGER
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* ID
* <P> Type: INTEGER (long) </P>
*/
public static final String PARENT_ID = "parent_id";
/**
* 便
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* 便
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> Type: INTEGER (long) </P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
* TEXT
* <P> Type: TEXT </P>
*/
public static final String SNIPPET = "snippet";
/**
* ID便
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_ID = "widget_id";
/**
*
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* ID
* <P> Type: INTEGER (long) </P>
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
* 01
* <P> Type: INTEGER </P>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* 便
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
* TYPE_NOTETYPE_FOLDER
* <P> Type: INTEGER </P>
*/
public static final String TYPE = "type";
/**
* ID
* <P> Type: INTEGER (long) </P>
*/
public static final String SYNC_ID = "sync_id";
/**
*
* <P> Type: INTEGER </P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* ID
* <P> Type : INTEGER </P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* gtaskIDTEXT
* <P> Type : TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
*
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
}
// DataColumns接口定义了存储笔记相关数据的数据表可能是更详细的数据表与NoteColumns对应的主表有所区分中各列的列名常量
public interface DataColumns {
/**
* ID
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* MIMETEXT
* <P> Type: Text </P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* ID便
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
*
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* TEXTMIME
* <P> Type: TEXT </P>
*/
public static final String CONTENT = "content";
/**
* {@link #MIMETYPE}
* <P> Type: INTEGER </P>
*/
public static final String DATA1 = "data1";
/**
* {@link #MIMETYPE}
* <P> Type: INTEGER </P>
*/
public static final String DATA2 = "data2";
/**
* {@link #MIMETYPE} TEXT
* <P> Type: TEXT </P>
*/
public static final String DATA3 = "data3";
/**
* {@link #MIMETYPE} TEXT
* <P> Type: TEXT </P>
*/
public static final String DATA4 = "data4";
/**
* {@link #MIMETYPE} TEXT
* <P> Type: TEXT </P>
*/
public static final String DATA5 = "data5";
}
// TextNote内部类继承自DataColumns接口主要用于定义文本笔记相关的特定属性、常量以及内容资源标识符Uri等信息
public static final class TextNote implements DataColumns {
/**
* 10
* <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;
// 表示文本笔记的内容类型用于在内容提供器等场景标识该类型数据格式符合Android的MIME类型规范
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
// 表示文本笔记的单个项目的内容类型同样符合MIME类型规范用于更具体的指向文本笔记中的单个条目
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 用于访问文本笔记相关数据的统一资源标识符Uri基于应用授权AUTHORITY和特定路径"/text_note")构建
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
// CallNote内部类同样继承自DataColumns接口用于定义通话记录笔记相关的特定属性、常量以及内容资源标识符Uri等内容
public static final class CallNote implements DataColumns {
/**
*
* <P> Type: INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1;
/**
* TEXT
* <P> Type: TEXT </P>
*/
public static final String PHONE_NUMBER = DATA3;
// 表示通话记录笔记的内容类型符合MIME类型规范用于标识该类型数据在内容提供器中的类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
// 表示通话记录笔记单个项目的内容类型符合MIME类型规范用于更具体指向通话记录笔记中的单个条目
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 用于访问通话记录笔记相关数据的统一资源标识符Uri基于应用授权AUTHORITY和特定路径"/call_note")构建
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -0,0 +1,279 @@
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 {
// 定义数据库文件名
private static final String DB_NAME = "note.db";
// 定义数据库版本号,用于数据库升级时的版本判断等操作
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";
// 采用单例模式,保存该类的唯一实例,确保整个应用中只有一个数据库帮助类实例在操作数据库
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中笔记IDDataColumns.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 + ");";
/**
* TriggerSQL
*
*/
/**
* NoteColumns.PARENT_IDSQL
* TABLE.NOTENoteColumns.PARENT_IDNoteColumns.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";
/**
* NoteColumns.PARENT_IDSQL
* TABLE.NOTENoteColumns.PARENT_IDNoteColumns.NOTES_COUNT0
*/
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";
/**
* SQL
* TABLE.NOTENoteColumns.PARENT_IDNoteColumns.NOTES_COUNT
*/
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";
/**
* SQL
* TABLE.NOTENoteColumns.NOTES_COUNT0ID
*/
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";
/**
* TABLE.DATA{@link DataConstants#NOTE}NoteColumns.SNIPPETSQL
* TABLE.DATADataColumns.CONTENT
*/
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";
/**
* TABLE.DATA{@link DataConstants#NOTE}NoteColumns.SNIPPETSQL
* TABLE.DATADataColumns.CONTENT
*/
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";
/**
* TABLE.DATA{@link DataConstants#NOTE}NoteColumns.SNIPPETSQL
* TABLE.DATA
*/
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";
/**
* TABLE.DATASQL
* TABLE.NOTETABLE.DATANoteColumns.IDID
*/
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";
/**
* SQL
* TABLE.NOTETABLE.NOTENoteColumns.PARENT_IDID
*/
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";
/**
* NoteColumns.PARENT_IDSQL
* TABLE.NOTEIDIDNotes.ID_TRASH_FOLERIDID
*/
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";
// NotesDatabaseHelper构造函数调用父类SQLiteOpenHelper的构造函数传入上下文、数据库名、游标工厂这里为null和数据库版本号
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 创建笔记表的方法接收一个SQLiteDatabase实例通过执行CREATE_NOTE_TABLE_SQL语句创建表
// 然后重新创建笔记表相关的触发器,并创建系统文件夹,最后记录日志表示笔记表已创建成功
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
// 重新创建笔记表相关触发器的方法先删除已存在的同名触发器如果有然后再执行创建各触发器的SQL语句
// 这样可以确保触发器的定义是最新的,避免因数据库结构变化等原因导致的问题
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);
}
// 创建系统文件夹的方法通过向笔记表TABLE.NOTE插入不同类型的系统文件夹记录来实现
// 每个文件夹记录设置了相应的ID和类型Notes.TYPE_SYSTEM用于构建应用中的默认文件夹结构
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);

@ -0,0 +1,300 @@
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是Android中用于在不同应用组件间共享数据的抽象基类
// 本类主要负责处理与笔记相关的数据操作,如查询、插入、删除、更新等,并与底层数据库进行交互
public class NotesProvider extends ContentProvider {
// UriMatcher用于根据传入的Uri来匹配相应的操作码以确定要执行何种数据操作
// 这里定义为静态成员变量方便在类的不同方法中使用相同的匹配规则来判断Uri类型
private static final UriMatcher mMatcher;
// NotesDatabaseHelper的实例用于辅助进行数据库相关操作例如获取数据库连接、执行SQL语句等
private NotesDatabaseHelper mHelper;
// 用于日志记录的标签,方便在查看日志时快速识别是该类中输出的日志信息
private static final String TAG = "NotesProvider";
// 以下定义了不同的整数常量作为不同类型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
// 一般用于对单个笔记进行操作的请求数字部分对应具体笔记的ID
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);
// 为搜索建议相关的Uri添加匹配规则用于处理搜索建议的请求路径
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
// 定义用于搜索结果投影Projection的字符串指定了从数据库查询搜索结果时要返回的列及格式
// 这里涉及到将笔记的一些字段进行处理后返回,例如去除换行符、设置图标、指定意图相关信息等
// 以便符合搜索结果展示和后续操作的要求
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;
// 定义用于查询笔记片段搜索的SQL语句模板基于上面的投影字符串构建
// 根据笔记片段SNIPPET字段进行模糊匹配查询同时添加了一些筛选条件如排除回收站文件夹、只查询笔记类型等
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的onCreate方法在内容提供器创建时调用
// 主要用于初始化NotesDatabaseHelper实例获取数据库操作的辅助对象若初始化成功则返回true表示创建成功
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
// 重写ContentProvider的query方法用于处理数据查询请求
// 根据传入的Uri、投影、选择条件等参数从数据库中查询相应的数据并返回结果集Cursor
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
// 获取可读数据库实例,用于执行查询操作,确保不会对数据库进行写操作
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
// 使用UriMatcher匹配传入的Uri根据匹配结果执行不同的查询逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 如果是URI_NOTE类型的Uri直接对笔记表TABLE.NOTE进行查询使用传入的投影、选择条件等参数
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
// 如果是URI_NOTE_ITEM类型的Uri先获取路径中的笔记ID即uri.getPathSegments().get(1)
// 然后在查询笔记表时添加基于笔记ID的筛选条件确保查询的是指定ID的单个笔记
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类型的Uri对数据表TABLE.DATA进行查询同样使用传入的相关参数
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
// 对于URI_DATA_ITEM类型的Uri获取路径中的数据ID然后在查询数据表时添加基于该数据ID的筛选条件
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类型
if (sortOrder!= null || projection!= null) {
// 若传入了排序顺序或投影参数,则抛出异常,因为对于这种类型的查询不应该指定这些参数
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
String searchString = null;
// 根据具体是搜索还是搜索建议的Uri类型获取不同方式传入的搜索字符串
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)) {
// 如果搜索字符串为空则直接返回null因为没有内容可查询
return null;
}
try {
// 对搜索字符串进行格式化,添加模糊匹配的通配符(%),用于构建模糊查询条件
searchString = String.format("%%%s%%", searchString);
// 使用格式化后的搜索字符串执行原始的SQL查询语句NOTES_SNIPPET_SEARCH_QUERY来获取搜索结果
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
} catch (IllegalStateException ex) {
// 如果在查询过程中出现异常,记录错误日志
Log.e(TAG, "got exception: " + ex.toString());
}
break;
default:
// 如果传入的Uri无法匹配任何已知的类型则抛出异常表示未知的Uri请求
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (c!= null) {
// 如果查询到了有效的结果集Cursor设置通知Uri以便在数据变化时通知相关观察者
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
// 重写ContentProvider的insert方法用于处理数据插入请求
// 根据传入的Uri和要插入的数据ContentValues将数据插入到相应的数据库表中并返回插入后数据的Uri
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
// 使用UriMatcher匹配传入的Uri根据匹配结果执行不同的插入逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 如果是URI_NOTE类型的Uri直接向笔记表TABLE.NOTE插入数据获取插入后笔记的ID
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
// 对于URI_DATA类型的Uri先检查要插入的数据中是否包含笔记IDDataColumns.NOTE_ID
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
// 如果没有笔记ID则记录警告日志表示数据格式错误
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
// 向数据表TABLE.DATA插入数据获取插入后数据的ID
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
// 如果传入的Uri无法匹配任何已知的类型则抛出异常表示未知的Uri请求
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果插入的笔记ID大于0即成功插入了笔记数据通知与笔记相关的Uri发生了变化
// 以便相关观察者如使用ContentObserver监听的组件可以做出响应
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 如果插入的数据ID大于0即成功插入了数据通知与数据相关的Uri发生了变化
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回插入数据后对应的Uri通过在原始Uri基础上添加插入数据的ID构建
return ContentUris.withAppendedId(uri, insertedId);
}
// 重写ContentProvider的delete方法用于处理数据删除请求
// 根据传入的Uri、选择条件等参数从相应的数据库表中删除匹配的数据并返回删除的行数
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
// 使用UriMatcher匹配传入的Uri根据匹配结果执行不同的删除逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 如果是URI_NOTE类型的Uri在原选择条件基础上添加笔记ID大于0的条件确保删除合法的笔记记录
// 然后从笔记表TABLE.NOTE中删除匹配的数据获取删除的行数
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 如果是URI_NOTE_ITEM类型的Uri获取路径中的笔记ID
id = uri.getPathSegments().get(1);
/**
* ID0
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
break;
}
// 根据笔记ID从笔记表中删除相应的记录获取删除的行数
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 针对URI_DATA类型的Uri从数据表TABLE.DATA中删除匹配的数据获取删除的行数并标记正在删除数据
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
case URI_DATA_ITEM:
// 对于URI_DATA_ITEM类型的Uri获取路径中的数据ID然后从数据表中删除相应的数据记录获取删除的行数并标记正在删除数据
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
// 如果传入的Uri无法匹配任何已知的类型则抛出异常表示未知的Uri请求
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
// 如果成功删除了数据删除行数大于0
if (deleteData) {
// 如果正在删除数据通知与笔记相关的Uri发生了变化因为数据的变化可能影响到笔记相关的展示等情况
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知与当前操作的Uri发生了变化以便相关观察者做出响应
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
// 重写ContentProvider的update方法用于处理数据更新请求
// 根据传入的Uri、要更新的数据ContentValues、选择条件等参数更新相应数据库表中的数据并返回更新的行数
@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;
// 使用UriMatcher匹配传入的Uri根据匹配结果执行不同的更新逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 如果是URI_NOTE类型的Uri先调用increaseNoteVersion方法来更新笔记的版本号可能用于版本控制等逻辑
increaseNoteVersion(-1, selection, selectionArgs);
// 然后根据传入的参数更新笔记表TABLE.NOTE中的数据获取更新的行数
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 如果是URI_NOTE_ITEM类型的Uri获取路径中的笔记ID
id = uri.getPathSegments().get(1);
// 调用increaseNoteVersion方法根据笔记ID来更新笔记的版本号
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
// 根据笔记ID和其他传入参数更新笔记表中的相应记录获取更新的行数
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 针对URI_DATA类型的Uri直接根据传入参数更新数据表TABLE.DATA中的数据获取更新的行数并标记正在更新数据
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
// 对于

@ -0,0 +1,222 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
// ResourceParser类主要用于解析和提供应用中各种与资源相关的信息例如背景颜色、字体大小以及不同界面元素对应的资源ID等方便在应用的不同地方统一获取和使用这些资源相关设置
public class ResourceParser {
// 定义表示黄色的常量用于标识某种颜色选项可能在背景颜色等设置中使用值为0可作为索引或标识值
public static final int YELLOW = 0;
// 定义表示蓝色的常量类似黄色的作用用于区分不同颜色选项值为1
public static final int BLUE = 1;
// 定义表示白色的常量值为2
public static final int WHITE = 2;
// 定义表示绿色的常量值为3
public static final int GREEN = 3;
// 定义表示红色的常量值为4
public static final int RED = 4;
// 定义默认的背景颜色的常量初始设置为黄色对应前面定义的YELLOW常量值表示在没有特定设置时的默认背景颜色选项
public static final int BG_DEFAULT_COLOR = YELLOW;
// 定义表示小字体的常量用于字体大小相关的标识值为0
public static final int TEXT_SMALL = 0;
// 定义表示中字体的常量值为1
public static final int TEXT_MEDIUM = 1;
// 定义表示大字体的常量值为2
public static final int TEXT_LARGE = 2;
// 定义表示超大字体的常量值为3
public static final int TEXT_SUPER = 3;
// 定义默认的字体大小的常量初始设置为中字体对应前面定义的TEXT_MEDIUM常量值表示在没有特定设置时的默认字体大小选项
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
// 内部静态类NoteBgResources用于管理笔记编辑界面相关的背景资源例如背景图片资源ID等
public static class NoteBgResources {
// 定义一个静态的整数数组存储笔记编辑界面背景图片资源ID对应不同的颜色选项按顺序依次对应前面定义的颜色常量顺序
private final static int[] BG_EDIT_RESOURCES = new int[] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
// 定义一个静态的整数数组存储笔记编辑界面标题背景图片资源ID同样对应不同的颜色选项
private final static int[] BG_EDIT_TITLE_RESOURCES = new int[] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
// 静态方法根据传入的索引id获取笔记编辑界面的背景图片资源ID返回对应的BG_EDIT_RESOURCES数组中的元素即资源ID
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
// 静态方法根据传入的索引id获取笔记编辑界面标题的背景图片资源ID返回对应的BG_EDIT_TITLE_RESOURCES数组中的元素即资源ID
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
// 静态方法用于获取默认的背景颜色对应的ID值根据应用的偏好设置Preference来决定返回值
public static int getDefaultBgId(Context context) {
// 获取应用默认的共享偏好设置对象,通过它可以读取用户之前设置的各种偏好选项
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 如果用户设置了特定的背景颜色选项通过对应的偏好设置键值判断这里假设为PREFERENCE_SET_BG_COLOR_KEY值为true表示已设置
// 则随机生成一个索引值范围是NoteBgResources类中背景资源数组的长度范围内作为返回的背景颜色ID
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
// 如果用户没有设置特定的背景颜色选项则返回默认的背景颜色ID前面定义的BG_DEFAULT_COLOR
return BG_DEFAULT_COLOR;
}
}
// 内部静态类NoteItemBgResources用于管理笔记列表项相关的背景资源如不同位置首个、中间、最后等的背景图片资源ID
public static class NoteItemBgResources {
// 定义一个静态的整数数组存储笔记列表项首个位置的背景图片资源ID对应不同的颜色选项
private final static int[] BG_FIRST_RESOURCES = new int[] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
// 定义一个静态的整数数组存储笔记列表项中间位置的背景图片资源ID对应不同的颜色选项
private final static int[] BG_NORMAL_RESOURCES = new int[] {
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
// 定义一个静态的整数数组存储笔记列表项最后位置的背景图片资源ID对应不同的颜色选项
private final static int[] BG_LAST_RESOURCES = new int[] {
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
// 定义一个静态的整数数组存储笔记列表项单个显示时可能没有前后相邻项的情况的背景图片资源ID对应不同的颜色选项
private final static int[] BG_SINGLE_RESOURCES = new int[] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
// 静态方法根据传入的索引id获取笔记列表项首个位置的背景图片资源ID返回对应的BG_FIRST_RESOURCES数组中的元素即资源ID
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
// 静态方法根据传入的索引id获取笔记列表项最后位置的背景图片资源ID返回对应的BG_LAST_RESOURCES数组中的元素即资源ID
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
// 静态方法根据传入的索引id获取笔记列表项单个显示时的背景图片资源ID返回对应的BG_SINGLE_RESOURCES数组中的元素即资源ID
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
// 静态方法根据传入的索引id获取笔记列表项中间位置的背景图片资源ID返回对应的BG_NORMAL_RESOURCES数组中的元素即资源ID
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
// 静态方法获取文件夹的背景图片资源ID返回固定的文件夹背景图片资源ID这里假设为R.drawable.list_folder
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
// 内部静态类WidgetBgResources用于管理应用小部件Widget相关的背景资源如不同尺寸小部件对应的背景图片资源ID
public static class WidgetBgResources {
// 定义一个静态的整数数组存储2x尺寸小部件的背景图片资源ID对应不同的颜色选项
private final static int[] BG_2X_RESOURCES = new int[] {
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
// 静态方法根据传入的索引id获取2x尺寸小部件的背景图片资源ID返回对应的BG_2X_RESOURCES数组中的元素即资源ID
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
// 定义一个静态的整数数组存储4x尺寸小部件的背景图片资源ID对应不同的颜色选项
private final static int[] BG_4X_RESOURCES = new int[] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
// 静态方法根据传入的索引id获取4x尺寸小部件的背景图片资源ID返回对应的BG_4X_RESOURCES数组中的元素即资源ID
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
// 内部静态类TextAppearanceResources用于管理文本外观相关的资源如不同字体大小对应的样式资源ID等
public static class TextAppearanceResources {
// 定义一个静态的整数数组存储不同字体大小对应的文本外观样式资源ID按顺序对应前面定义的字体大小常量顺序
private final static int[] TEXTAPPEARANCE_RESOURCES = new int[] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
// 静态方法根据传入的索引id获取文本外观样式资源ID同时进行了边界检查如果传入的id超出资源数组长度范围则返回默认的字体大小对应的资源ID避免出现资源获取异常情况
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
// 静态方法,获取文本外观样式资源数组的长度,用于判断资源数量或者进行循环等操作时参考
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

@ -0,0 +1,236 @@
/*
* 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.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
// SqlData类主要用于处理与数据库中相关数据的交互操作能够从数据库游标加载数据、设置和获取JSON格式的数据内容以及将数据的变更提交到数据库等
public class SqlData {
// 用于日志记录的标签方便在Log输出中识别该类相关的日志信息使用类的简单名称作为标签
private static final String TAG = SqlData.class.getSimpleName();
// 定义一个表示无效ID的值通常用于初始化或者标识某个尚未正确赋值的ID情况
private static final int INVALID_ID = -99999;
// 定义查询数据时的投影(即要获取的列)数组,指定了从数据库获取数据时包含的列名
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
// 定义在投影数组中数据ID列对应的索引位置方便后续从游标中获取对应列的值
public static final int DATA_ID_COLUMN = 0;
// 定义在投影数组中数据MIME_TYPE列对应的索引位置
public static final int DATA_MIME_TYPE_COLUMN = 1;
// 定义在投影数组中数据CONTENT列对应的索引位置
public static final int DATA_CONTENT_COLUMN = 2;
// 定义在投影数组中数据DATA1列对应的索引位置
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
// 定义在投影数组中数据DATA3列对应的索引位置
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
// 用于与内容提供器进行交互,用于执行如插入、更新等数据操作
private ContentResolver mContentResolver;
// 标记当前对象所代表的数据是否是新创建的初始化为true表示新创建状态
private boolean mIsCreate;
// 存储数据的ID初始化为无效ID值
private long mDataId;
// 存储数据的MIME类型初始化为默认的NOTE类型可能来自DataConstants类
private String mDataMimeType;
// 存储数据的具体内容,初始化为空字符串
private String mDataContent;
// 存储数据内容中相关的一个长整型数据具体含义可能根据业务而定初始化为0
private long mDataContentData1;
// 存储数据内容中相关的一个字符串数据(具体含义可能根据业务而定),初始化为空字符串
private String mDataContentData3;
// 用于存储数据发生变更的内容值,方便后续批量提交变更到数据库
private ContentValues mDiffDataValues;
// 构造函数用于创建一个新的SqlData实例通常用于创建新的数据对象情况传入上下文用于获取内容解析器
public SqlData(Context context) {
// 获取上下文对应的内容解析器,用于后续与内容提供器交互操作
mContentResolver = context.getContentResolver();
// 标记为新创建状态
mIsCreate = true;
// 设置初始的无效数据ID
mDataId = INVALID_ID;
// 设置默认的MIME类型
mDataMimeType = DataConstants.NOTE;
// 设置初始的空内容字符串
mDataContent = "";
// 设置初始的相关数据值为0
mDataContentData1 = 0;
// 设置初始的相关数据字符串为空
mDataContentData3 = "";
// 初始化用于存储变更数据值的ContentValues对象
mDiffDataValues = new ContentValues();
}
// 另一个构造函数用于根据已有的数据库游标创建SqlData实例通常用于从数据库读取现有数据并封装到该对象中传入上下文和游标
public SqlData(Context context, Cursor c) {
// 获取上下文对应的内容解析器
mContentResolver = context.getContentResolver();
// 标记为非新创建状态,因为是从现有数据构建
mIsCreate = false;
// 从游标中加载数据到该对象的各个属性中
loadFromCursor(c);
// 初始化用于存储变更数据值的ContentValues对象
mDiffDataValues = new ContentValues();
}
// 从给定的游标中读取数据并设置到当前对象的对应属性中,根据之前定义的列索引位置获取相应的值
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
// 根据传入的JSON对象设置当前对象的数据内容比较并更新有变化的数据项到mDiffDataValues中用于后续提交变更
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID)? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId!= dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE)? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate ||!mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT)? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate ||!mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1)? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1!= dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3)? js.getString(DataColumns.DATA3) : "";
if (mIsCreate ||!mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
// 将当前对象的数据内容转换为JSON对象返回如果是新创建状态尚未在数据库中存在则记录错误日志并返回null
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
// 将数据的变更提交到数据库中,根据是新创建还是已有数据执行不同的操作(插入或更新),同时处理版本验证等逻辑
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
// 如果是新创建数据且当前设置的ID为无效ID同时变更数据值中包含了ID字段则移除该ID字段可能是由数据库自动生成等情况
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
// 设置关联的笔记ID到变更数据值中以便插入时建立关联
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
// 通过内容解析器向指定的数据库URI插入数据获取插入后数据对应的URI
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
// 尝试从插入后的URI中获取插入数据的ID设置到当前对象的mDataId属性中
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 如果获取ID过程出现格式转换异常记录错误日志并抛出操作失败异常表示创建笔记失败
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
// 如果是已有数据且有变更数据值(即有数据需要更新)
if (mDiffDataValues.size() > 0) {
int result = 0;
// 如果不需要验证版本
if (!validateVersion) {
// 直接执行更新操作更新对应数据ID的数据不做版本相关验证
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
// 如果需要验证版本则执行带版本验证条件的更新操作根据指定的笔记ID和版本号进行更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
"? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
// 如果更新操作没有实际更新任何数据,记录警告日志,可能是用户在同步时已经更新了笔记等情况
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
// 清除变更数据值,准备下一次的数据变更操作
mDiffDataValues.clear();
// 将创建标记设置为false表示当前数据已不是新创建状态
mIsCreate = false;
}
// 获取当前数据对象的ID值
public long getId() {
return mDataId;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 KiB

Loading…
Cancel
Save