From 1db36409fc87f6b0fc853d43e583ec860f8ac814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E9=B9=BF=E7=91=83?= <519543323@qq.com> Date: Fri, 19 Apr 2024 19:00:30 +0800 Subject: [PATCH] codeMark --- AlarmInitReceiver.java | 71 +++++ Contact.java | 81 ++++++ DateTimePicker.java | 607 +++++++++++++++++++++++++++++++++++++++++ NotesProvider.java | 361 ++++++++++++++++++++++++ 4 files changed, 1120 insertions(+) create mode 100644 AlarmInitReceiver.java create mode 100644 Contact.java create mode 100644 DateTimePicker.java create mode 100644 NotesProvider.java diff --git a/AlarmInitReceiver.java b/AlarmInitReceiver.java new file mode 100644 index 0000000..515f905 --- /dev/null +++ b/AlarmInitReceiver.java @@ -0,0 +1,71 @@ +//该类是广播接收器,用于在应用启动时初始化提醒设置。 +//当系统启动时,它会检查数据库中所有设置了提醒的笔记,并为每个笔记设置相应的提醒。 + +package net.micode.notes.ui; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + +public class AlarmInitReceiver extends BroadcastReceiver { + + // 查询笔记时需要的列 + private static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + //对数据库的操作,调用标签ID和闹钟时间 + // 列的索引 + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + /** + * 当接收到广播时执行的操作。主要用于设置所有已记录的提醒时间。 + * + * @param context 上下文,提供访问应用全局功能的入口。 + * @param intent 携带了触发该接收器的广播信息。 + */ + @Override + public void onReceive(Context context, Intent intent) { + // 获取当前日期和时间 + long currentDate = System.currentTimeMillis(); + //System.currentTimeMillis()产生一个当前的毫秒 + // 查询数据库中所有需要提醒的笔记 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String[]{String.valueOf(currentDate)}, + //将long变量currentDate转化为字符串 + null); + ////Cursor通过查找数据库中的标签内容,找到和当前系统时间相等的标签 + + if (c != null) { + // 遍历查询结果,为每个需要提醒的笔记设置提醒 + if (c.moveToFirst()) { + do { + // 获取提醒日期 + long alertDate = c.getLong(COLUMN_ALERTED_DATE); + // 创建Intent,用于在提醒时间触发AlarmReceiver + Intent sender = new Intent(context, AlarmReceiver.class); + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + // 创建PendingIntent,它是一个延迟的意图,可以在特定时间由系统触发 + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + // 获取AlarmManager服务,用于设置提醒 + AlarmManager alermManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + // 设置提醒 + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); + } + // 关闭Cursor,释放资源 + c.close(); + } + //根据数据库里的闹钟时间创建一个闹钟机制 + } +} diff --git a/Contact.java b/Contact.java new file mode 100644 index 0000000..75f7701 --- /dev/null +++ b/Contact.java @@ -0,0 +1,81 @@ +/** + * 该类实现了通过电话号码查询联系人信息的功能,包括: + * 从联系人数据库中获取与特定电话号码相关联的显示名称。 + * 使用缓存机制减少数据库查询次数,提高性能。 + * 根据电话号码格式化查询条件,并执行数据库查询操作。 + * 处理查询结果,将联系人名称添加到缓存中,以便下次查询时直接获取。 + * 记录日志以便跟踪查询过程中的问题。 + */ + +package net.micode.notes.data; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; + +public class Contact { + // 缓存已查询过的电话号码和对应的联系人名称,以减少数据库查询次数。 + private static HashMap sContactCache; + private static final String TAG = "Contact"; + // 用于查询具有完整国际号码格式的电话号码的selection字符串。 + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + + + public static String getContact(Context context, String phoneNumber) { + //这段代码是一个联系人管理类,用于获取手机联系人的信息。它通过提供一个静态方法 getContact 来获取指定手机号码对应的联系人姓名 + // 初始化或获取联系人缓存 + if (sContactCache == null) { + sContactCache = new HashMap(); + } + + // 从缓存中直接获取联系人名称,如果存在。 + if (sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + + // 使用PhoneNumberUtils将电话号码格式化为适合查询的形式 + String selection = CALLER_ID_SELECTION.replace("+",//构造一个需要查询的联系人名 + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + + // 执行查询以获取与电话号码相关联的联系人名称 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, + new String[]{Phone.DISPLAY_NAME}, + selection, + new String[]{phoneNumber}, + null); + + if (cursor != null && cursor.moveToFirst()) { + //查询成功且有结果,就从 Cursor 中获取联系人姓名,并将结果存入 sContactCache 中作为缓存 + 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 { + // 如果查询失败或无结果,则返回null + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} +/*首先检查一个静态哈希表 ContactCache 中是否已经存在该手机号码的联系人信息, +如果存在,则直接返回缓存的结果。如果不存在,则根据给定的手机号码查询系统数据库,获取联系人姓名*/ \ No newline at end of file diff --git a/DateTimePicker.java b/DateTimePicker.java new file mode 100644 index 0000000..3385dfc --- /dev/null +++ b/DateTimePicker.java @@ -0,0 +1,607 @@ +/* + * 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.ui; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +import net.micode.notes.R; + + +import android.content.Context; +import android.text.format.DateFormat; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.NumberPicker; + +public class DateTimePicker extends FrameLayout { + + // 默认启用状态 + private static final boolean DEFAULT_ENABLE_STATE = true; + + // 半天的小时数 + private static final int HOURS_IN_HALF_DAY = 12; + // 一整天的小时数 + private static final int HOURS_IN_ALL_DAY = 24; + // 一周的天数 + private static final int DAYS_IN_ALL_WEEK = 7; + // 日期选择器的最小值 + private static final int DATE_SPINNER_MIN_VAL = 0; + // 日期选择器的最大值 + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + // 24小时制小时选择器的最小值 + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + // 24小时制小时选择器的最大值 + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + // 12小时制小时选择器的最小值 + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + // 12小时制小时选择器的最大值 + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + // 分钟选择器的最小值 + private static final int MINUT_SPINNER_MIN_VAL = 0; + // 分钟选择器的最大值 + private static final int MINUT_SPINNER_MAX_VAL = 59; + // 上下午选择器的最小值 + private static final int AMPM_SPINNER_MIN_VAL = 0; + // 上下午选择器的最大值 + private static final int AMPM_SPINNER_MAX_VAL = 1; + //初始化控件 + // 日期选择器 + private final NumberPicker mDateSpinner; + // 小时选择器 + private final NumberPicker mHourSpinner; + // 分钟选择器 + private final NumberPicker mMinuteSpinner; + // 上下午选择器 + private final NumberPicker mAmPmSpinner; + // 当前日期 + //NumberPicker是数字选择器 + //这里定义的四个变量全部是在设置闹钟时需要选择的变量 + private Calendar mDate; + //定义了Calendar类型的变量mDate,用于操作时间 + // 用于显示日期的字符串数组 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + // 当前是否为上午 + private boolean mIsAm; + + // 当前是否为24小时制视图 + private boolean mIs24HourView; + + // 控件是否启用 + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + // 是否正在初始化 + private boolean mInitialising; + + // 日期时间改变监听器 + private OnDateTimeChangedListener mOnDateTimeChangedListener; + //OnValueChangeListener,是时间改变监听器,对日期的监听 + //将现在日期的值传递给mDate;updateDateControl是同步操作 + // 日期选择器的值改变监听器 + private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 根据新旧值的差异更新日期 + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + updateDateControl(); + onDateTimeChanged(); + } + }; + + // 小时选择器的值改变监听器 + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 根据小时的变化更新日期和上下午状态,对小时Hour的监听 + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + //声明一个Calendar的变量cal,便于后续的操作 + if (!mIs24HourView) { + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + //对于12小时制晚上11点和12点交替时对日期的更改 + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + //这对于12小时制凌晨11点和12点交替时对日期的更改 + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + //对于12小时制中午11点和12点交替时对AM和PM的更改 + } else { + // 处理24小时制下的日期变化 + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + // 更新小时并触发日期时间改变事件 + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + //通过数字选择器对newHour的赋值 + mDate.set(Calendar.HOUR_OF_DAY, newHour); + //通过set函数将新的Hour值传给mDate + onDateTimeChanged(); + // 如果日期有变化,则更新年月日 + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + //OnValueChangeListener,这是时间改变监听器,这里主要是对日期的监听 + //将现在日期的值传递给mDate;updateDateControl是同步操作 + + + // 分别为分钟和AM/PM选择器监听器设置匿名内部类,实现数值变化时的处理逻辑。 + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 计算小时的偏移量,当从最大值变为最小值或从最小值变为最大值时调整 + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + //设置offset,作为小时改变的一个记录数据 + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + } + // 根据偏移量更新日期和小时选择器,并检查是否需要切换AM/PM + //如果原值为59,新值为0,则offset加1 + //如果原值为0,新值为59,则offset减1 + if (offset != 0) { + mDate.add(Calendar.HOUR_OF_DAY, offset); + mHourSpinner.setValue(getCurrentHour()); + updateDateControl(); + int newHour = getCurrentHourOfDay(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + // 更新分钟值并触发日期变化的回调 + mDate.set(Calendar.MINUTE, newVal); + onDateTimeChanged(); + } + }; + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + //对AM和PM的监听 + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 切换AM/PM状态,并更新日期和AM/PM选择器 + mIsAm = !mIsAm; + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + onDateTimeChanged(); + } + }; + + // 定义日期时间变化的回调接口 + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + // 构造函数:初始化日期时间选择器 + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + } + //通过对数据库的访问,获取当前的系统时间 + // 构造函数:指定初始日期时间 + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); + } + + // 构造函数:指定是否使用24小时制视图 + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + //获取系统时间 + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + + // 初始化日期、小时、分钟和AM/PM选择器,并设置相应的监听器 + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); + mAmPmSpinner.setDisplayedValues(stringsForAmPm); + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); + + // 更新控件至初始状态 + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView); + + // 设置为当前时间 + setCurrentDate(date); + + setEnabled(isEnabled()); + + // 设置内容描述 + mInitialising = false; + } + + + /** + * 设置控件的启用状态。 + * 如果当前状态与传入状态相同,则不进行任何操作。 + * 启用或禁用日期和时间选择器,并更新内部启用状态。 + * + * @param enabled 控件是否启用 + */ + @Override + public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; + } + super.setEnabled(enabled); + // 同时启用或禁用日期和时间选择器 + mDateSpinner.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; + } + //存在疑问!!!!!!!!!!!!!setEnabled的作用 + //下面的代码通过原程序的注释已经比较清晰,另外可以通过函数名来判断 + //下面的各函数主要是对上面代码引用到的各函数功能的实现 + + /** + * 获取控件的启用状态。 + * + * @return 控件是否启用 + */ + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * 获取当前日期的时间戳(毫秒)。 + * + * @return 当前日期的毫秒时间戳 + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + } + //实现函数得到当前的秒数 + + /** + * 设置当前日期。 + * 根据传入的毫秒时间戳更新日期选择器的值。 + * + * @param date The current date in millis + */ + public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + // 通过日历实例的详细字段设置当前日期和时间 + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); + } + //实现函数功能,设置当前的时间,参数是date + + /** + * 设置当前日期和时间。 + * 分别设置年、月、日、时和分。 + * + * @param year 当前年份 + * @param month 当前月份 + * @param dayOfMonth 当前日 + * @param hourOfDay 当前小时 + * @param minute 当前分钟 + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + // 分别设置年、月、日、时和分 + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); + } +//实现函数功能,设置当前的时间,参数是各详细的变量 + + /** + * 获取当前年份 + * + * @return 当前的年份 + */ + + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + //得到year、month、day等值 + /** + * 设置当前年份 + * + * @param year 当前的年份 + */ + public void setCurrentYear(int year) { + // 如果不是初始化状态并且设置的年份与当前年份相同,则直接返回 + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); // 更新日期控件 + onDateTimeChanged(); // 触发日期时间改变事件 + } + + /** + * 获取当前月份 + * + * @return 当前的月份(从0开始) + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + /** + * 设置当前月份 + * + * @param month 当前的月份(从0开始) + */ + public void setCurrentMonth(int month) { + // 如果不是初始化状态并且设置的月份与当前月份相同,则直接返回 + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); // 更新日期控件 + onDateTimeChanged(); // 触发日期时间改变事件 + } + + /** + * 获取当前日期(月中的天数) + * + * @return 当前的日期(月中的天数) + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * 设置当前日期(月中的天数) + * + * @param dayOfMonth 当前的日期(月中的天数) + */ + public void setCurrentDay(int dayOfMonth) { + // 如果不是初始化状态并且设置的日期与当前日期相同,则直接返回 + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); // 更新日期控件 + onDateTimeChanged(); // 触发日期时间改变事件 + } + + /** + * 获取当前小时(24小时制),范围为(0~23) + * + * @return 当前的小时(24小时制) + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + /** + * 获取当前小时,根据是否为24小时制返回不同的值。 + * 如果是24小时制,与{@link #getCurrentHourOfDay()}返回相同结果; + * 否则,将小时转换为12小时制,并考虑上午/下午。 + * + * @return 当前的小时(根据视图模式可能是12小时制) + */ + private int getCurrentHour() { + if (mIs24HourView) { + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + // 转换为12小时制 + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + + /** + * 设置当前小时(24小时制),范围为(0~23) + * + * @param hourOfDay 当前小时数 + */ + public void setCurrentHour(int hourOfDay) { + // 如果在初始化中或者小时未改变,则直接返回 + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + // 如果不是24小时视图,则调整小时数并更新AM/PM控制 + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); + } + + /** + * 获取当前分钟数 + * + * @return 当前分钟数 + */ + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + } + + /** + * 设置当前分钟数 + * + * @param minute 当前分钟数值 + */ + public void setCurrentMinute(int minute) { + // 如果在初始化中或者分钟数未改变,则直接返回 + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); + } + + /** + * 获取当前是否为24小时视图 + * + * @return 如果是24小时视图返回true,否则返回false + */ + public boolean is24HourView() { + return mIs24HourView; + } + + /** + * 设置当前视图为24小时制还是AM/PM制 + * + * @param is24HourView 如果为true表示24小时制,false表示AM/PM制 + */ + public void set24HourView(boolean is24HourView) { + // 如果视图模式未改变,则直接返回 + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); + } + + /** + * 更新日期控制组件,显示正确的日期选项 + */ + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); + mDateSpinner.setDisplayedValues(null); + // 循环设置一周内每一天的显示文本 + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate(); + } + // 对于星期几的算法 + + /** + * 根据当前是否为24小时视图来更新AM/PM控制组件的显示和值 + */ + private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } + } + // 对于上下午操作的算法 + + /** + * 根据当前是否为24小时视图来更新小时控制组件的最小值和最大值 + */ + private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } + } + // 对与小时的算法 + + /** + * 设置点击“设置”按钮时的回调接口 + * + * @param callback 回调接口实例,如果为null则不执行任何操作 + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { + mOnDateTimeChangedListener = callback; + } + + /** + * 当日期时间被改变时调用此方法,如果设置了日期时间改变监听器,则触发监听器的回调方法 + */ + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} diff --git a/NotesProvider.java b/NotesProvider.java new file mode 100644 index 0000000..1386b39 --- /dev/null +++ b/NotesProvider.java @@ -0,0 +1,361 @@ +/** + * - 数据通知:在数据改变时,通过ContentResolver发送通知,以便相关的观察者可以及时更新数据。 + * - 搜索建议:支持根据关键词模糊搜索笔记的标题和内容,并提供搜索建议功能。 + */ +package net.micode.notes.data; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; +//这段代码用于管理笔记的数据,提供了对笔记的查询、插入、更新和删除等操作 +public class NotesProvider extends ContentProvider { + //这段代码实现了一个ContentProvider,其中包含了对数据的查询、插入、删除和更新操作 + private static final UriMatcher mMatcher; + + private NotesDatabaseHelper mHelper; + + private static final String TAG = "NotesProvider"; + + private static final int URI_NOTE = 1; + private static final int URI_NOTE_ITEM = 2; + private static final int URI_DATA = 3; + private static final int URI_DATA_ITEM = 4; + + private static final int URI_SEARCH = 5; + private static final int URI_SEARCH_SUGGEST = 6; + + // 初始化UriMatcher,用于匹配不同的URI请求 + static { + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + } + + 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语句 + 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被创建时调用,用于初始化数据库帮助类。 + * + * @return 总是返回true。 + */ + @Override + public boolean onCreate() { + mHelper = NotesDatabaseHelper.getInstance(getContext()); + //代码通过调用 getContext() 方法获取当前上下文,并使用 + //获取一个数据库帮助类的实例,将得到的数据库帮助类实例赋值给成员变量mHelper + return true; + } + + /** + * 根据URI查询数据。 + * + * @param uri 要查询的数据的URI。 + * @param projection 要返回的列。 + * @param selection 查询条件。 + * @param selectionArgs 用于查询条件的参数。 + * @param sortOrder 排序顺序。 + * @return 返回匹配的Cursor对象。 + */ + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + //用于查询数据。接收一个Uri参数,这个Uri参数用于指定要查询的数据。 + // 通过查询数据库并返回一个Cursor对象,可以获取到相应的数据 + Cursor c = null; + SQLiteDatabase db = mHelper.getReadableDatabase(); + String id = null; + // 根据URI匹配查询类型 + switch (mMatcher.match(uri)) { + case URI_NOTE: + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + case URI_DATA: + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + case URI_SEARCH: + case URI_SEARCH_SUGGEST: + // 处理搜索建议的特殊逻辑 + if (sortOrder != null || projection != null) { + throw new IllegalArgumentException( + "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); + } + + String searchString = null; + if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + if (uri.getPathSegments().size() > 1) { + searchString = uri.getPathSegments().get(1); + } + } else { + searchString = uri.getQueryParameter("pattern"); + } + + if (TextUtils.isEmpty(searchString)) { + return null; + } + + try { + searchString = String.format("%%%s%%", searchString); + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, + new String[]{searchString}); + } catch (IllegalStateException ex) { + Log.e(TAG, "got exception: " + ex.toString()); + } + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + // 设置通知URI,以便数据改变时可以通知 + if (c != null) { + c.setNotificationUri(getContext().getContentResolver(), uri); + } + return c; + } +//查询:根据不同的URI进行查询,包括查询全部笔记、单个笔记、全部数据和单个数据 + + @Override + public Uri insert(Uri uri, ContentValues values) { + //用于插入数据。接收一个Uri参数和一个ContentValues参数,用于指定要插入的数据。 + //通过将数据插入到数据库中,并返回新插入数据的Uri,可以实现数据的插入操作 + SQLiteDatabase db = mHelper.getWritableDatabase(); + long dataId = 0, noteId = 0, insertedId = 0; + switch (mMatcher.match(uri)) { + case URI_NOTE: + insertedId = noteId = db.insert(TABLE.NOTE, null, values); + break; + case URI_DATA: + if (values.containsKey(DataColumns.NOTE_ID)) { + noteId = values.getAsLong(DataColumns.NOTE_ID); + } else { + Log.d(TAG, "Wrong data format without note id:" + values.toString()); + } + insertedId = dataId = db.insert(TABLE.DATA, null, values); + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + // 通知URI改变 + if (noteId > 0) { + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); + } + + if (dataId > 0) { + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); + } + + return ContentUris.withAppendedId(uri, insertedId); + }//插入:插入新的笔记或数据到数据库中,并返回插入数据的URI。 + + /** + * 根据URI删除数据。 + * + * @param uri 要删除数据的URI。 + * @param selection 删除条件。 + * @param selectionArgs 用于删除条件的参数。 + * @return 返回被删除的行数。 + */ + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + //用于删除数据。它接收一个Uri参数和一个可选的Selection和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小于等于0的笔记是系统文件夹,不允许删除 + */ + 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); + } + // 通知URI改变 + if (count > 0) { + if (deleteData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } + getContext().getContentResolver().notifyChange(uri, null); + } + return count; + } +//删除:删除符合条件的笔记或数据,并返回被删除的行数。 + /** + * 更新数据库中的数据。 + * + * @param uri 要更新数据的URI。 + * @param values 要更新到的数据。 + * @param selection 更新条件。 + * @param selectionArgs 用于更新条件的参数。 + * @return 返回被更新的行数。 + */ + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + //用于更新数据。接收一个Uri参数、一个ContentValues参数和一个可选的Selection和SelectionArgs参数,用于指定要更新的数据和更新条件。 + //通过在数据库中执行更新操作,并返回受影响的行数,可以实现数据的更新操作 + int count = 0; + String id = null; + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean updateData = false; + switch (mMatcher.match(uri)) { + case URI_NOTE: + increaseNoteVersion(-1, selection, selectionArgs); + count = db.update(TABLE.NOTE, values, selection, selectionArgs); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + break; + case URI_DATA: + count = db.update(TABLE.DATA, values, selection, selectionArgs); + updateData = true; + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + updateData = true; + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + //更新:更新符合条件的笔记或数据,并返回被更新的行数。更新笔记时会增加笔记的版本号。 + + // 通知URI改变 + if (count > 0) { + if (updateData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } + getContext().getContentResolver().notifyChange(uri, null); + } + return count; + } + + + /** + * 解析选择条件,如果存在选择条件,则在条件前后添加" AND (" 和 ')'。 + * + * @param selection 用户提供的选择条件。 + * @return 如果有选择条件,则返回添加了定界符的选择条件字符串;否则返回空字符串。 + */ + private String parseSelection(String selection) { + return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + } + + /** + * 增加指定笔记的版本号。 + * + * @param id 笔记的ID,如果为正数,则根据ID更新版本号;如果为0或负数,则根据selection参数更新版本号。 + * @param selection 用于选择需要更新的笔记的条件字符串,可以为空。 + * @param selectionArgs 与selection参数配合使用的参数数组,用于替换selection中的"?"。 + */ + private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(TABLE.NOTE); + sql.append(" SET "); + sql.append(NoteColumns.VERSION); + sql.append("=" + NoteColumns.VERSION + "+1 "); + + // 根据ID或选择条件构建SQL的WHERE子句 + if (id > 0 || !TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + } + if (id > 0) { + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); + } + if (!TextUtils.isEmpty(selection)) { + String selectString = id > 0 ? parseSelection(selection) : selection; + // 替换selection中的"?"为selectionArgs中的对应参数 + for (String args : selectionArgs) { + selectString = selectString.replaceFirst("\\?", args); + } + sql.append(selectString); + } + + mHelper.getWritableDatabase().execSQL(sql.toString()); + } + + /** + * 根据URI获取对应的MIME类型。 + * 本方法是个待实现的方法,当前仅返回null。 + * + * @param uri 请求的URI。 + * @return 返回null,表示该方法尚未实现。 + */ + @Override + public String getType(Uri uri) { + //用于获取数据的类型。 + // TODO Auto-generated method stub + return null; + } + +}