Compare commits

...

26 Commits

@ -0,0 +1,159 @@
/*
* 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 android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
// AlarmAlertActivity 是一个用于显示便签提醒的 Activity。
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId; // 便签的ID。
private String mSnippet; // 便签的预览文本。
private static final int SNIPPET_PREW_MAX_LEN = 60; // 预览文本的最大长度。
MediaPlayer mPlayer; // 用于播放提醒声音的 MediaPlayer。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求无标题栏的窗口特性。
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 锁屏时显示窗口。
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); // 屏幕关闭时点亮屏幕。
}
Intent intent = getIntent(); // 获取启动该 Activity 的 Intent。
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 从 Intent 中获取便签 ID。
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 获取便签预览文本。
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet; // 预览文本超出最大长度时截断。
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer(); // 初始化 MediaPlayer。
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog(); // 显示操作对话框。
playAlarmSound(); // 播放提醒声音。
} else {
finish(); // 如果便签不可见,则结束 Activity。
}
}
// 检查屏幕是否开启。
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
// 播放提醒声音。
private void playAlarmSound() {
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 显示操作对话框。
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
dialog.show().setOnDismissListener(this);
}
// DialogInterface.OnClickListener 的实现,处理对话框按钮点击事件。
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
break;
}
}
// DialogInterface.OnDismissListener 的实现,处理对话框消失事件。
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
// 停止提醒声音。
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}

@ -0,0 +1,75 @@
/*
* 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 android.app.AlarmManager; // 引入AlarmManager类用于设置闹钟
import android.app.PendingIntent; // 引入PendingIntent类表示延迟的Intent
import android.content.BroadcastReceiver; // 引入BroadcastReceiver类用于接收广播
import android.content.ContentUris; // 引入ContentUris类用于生成URI
import android.content.Context; // 引入Context类用于访问系统资源
import android.content.Intent; // 引入Intent类用于启动组件
import android.database.Cursor; // 引入Cursor类用于数据库查询
import net.micode.notes.data.Notes; // 引入Notes类处理笔记数据
import net.micode.notes.data.Notes.NoteColumns; // 引入NoteColumns类表示Notes表的列
public class AlarmInitReceiver extends BroadcastReceiver { // 定义一个继承自BroadcastReceiver的类
// 定义查询需要的列
private static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 获取笔记的ID列
NoteColumns.ALERTED_DATE // 获取提醒时间列
};
// 列索引常量
private static final int COLUMN_ID = 0; // 第0列是ID列
private static final int COLUMN_ALERTED_DATE = 1; // 第1列是提醒时间列
@Override
public void onReceive(Context context, Intent intent) { // 重写onReceive方法接收广播
long currentDate = System.currentTimeMillis(); // 获取当前的系统时间(以毫秒为单位)
// 查询数据库,获取需要提醒的笔记,且提醒时间大于当前时间
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, // 查询URI为Notes.CONTENT_NOTE_URI的内容提供者
PROJECTION, // 需要查询的列
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, // 查询条件:提醒时间大于当前时间,且类型为笔记
new String[] { String.valueOf(currentDate) }, // 查询条件中的当前时间
null); // 不需要排序
// 如果查询结果不为空
if (c != null) {
// 如果查询到至少一条记录
if (c.moveToFirst()) {
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE); // 获取提醒时间
Intent sender = new Intent(context, AlarmReceiver.class); // 创建一个新的Intent用于发送广播
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); // 设置Intent的数据笔记的ID
// 创建PendingIntent表示在指定时间执行AlarmReceiver广播
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取系统的AlarmManager服务
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// 设置闹钟使用RTC_WAKEUP模式当到达指定的提醒时间时唤醒设备并触发PendingIntent
alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext()); // 如果查询结果中还有下一条记录,则继续处理
}
c.close(); // 关闭Cursor
}
}
}

@ -0,0 +1,35 @@
/*
* 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
// AlarmReceiver 类继承自 BroadcastReceiver用于接收便签提醒的广播事件。
public class AlarmReceiver extends BroadcastReceiver {
// onReceive 方法是 BroadcastReceiver 的回调方法,当接收到广播时被调用。
@Override
public void onReceive(Context context, Intent intent) {
// 设置 intent 的目标类为 AlarmAlertActivity。
intent.setClass(context, AlarmAlertActivity.class);
// 为 intent 添加 FLAG_ACTIVITY_NEW_TASK 标志,使得在新的任务栈中启动 Activity。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 在给定的上下文中启动 AlarmAlertActivity。
context.startActivity(intent);
}
}

@ -0,0 +1,275 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
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 java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
// BackupUtils 类提供备份和恢复便签数据的功能。
public class BackupUtils {
private static final String TAG = "BackupUtils";
// 单例模式
private static BackupUtils sInstance;
// 获取 BackupUtils 单例对象
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
*
*/
// 当前 sdcard 没有挂载
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 备份文件不存在
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 数据格式不正确,可能被其他程序修改
public static final int STATE_DATA_DESTROIED = 2;
// 一些运行时异常导致恢复或备份失败
public static final int STATE_SYSTEM_ERROR = 3;
// 备份或恢复成功
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
// BackupUtils 构造函数
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
// 检查外部存储是否可用
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 导出数据为文本
public int exportToText() {
return mTextExport.exportToText();
}
// 获取导出的文本文件名
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
// 获取导出的文本文件目录
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
// TextExport 内部类,处理文本导出
private static class TextExport {
// 省略部分代码...
// TextExport 构造函数
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
// 获取格式化字符串
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
// 导出文件夹到文本
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询属于此文件夹的便签
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// 打印便签的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于此便签的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
// 导出便签到打印流
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询属于此便签的数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// 打印电话号码
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// 打印通话日期
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// 打印通话附件位置
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// 在便签之间打印分隔线
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
// 导出文本,用户可读
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 首先导出文件夹及其便签
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// 打印文件夹名称
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// 导出根文件夹中的便签
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于此便签的数据
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
// 获取指向导出文本文件的打印流
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();}
return null;
}
}

@ -0,0 +1,245 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
// DataUtils 类提供了一系列用于操作便签数据的工具方法。
public class DataUtils {
public static final String TAG = "DataUtils";
// 批量删除便签的方法。
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
// 将便签移动到指定文件夹的方法。
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
// 批量将便签移动到指定文件夹的方法。
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
// 获取用户文件夹数量的方法。
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close();
}
}
}
return count;
}
// 检查便签在数据库中是否存在的方法。
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 检查便签在数据库中是否存在的方法。
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 检查数据在数据库中是否存在的方法。
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 检查文件夹名称是否可见的方法。
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 获取文件夹便签小部件属性的方法。
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>();
do {
try {
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
set.add(widget);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
c.close();
}
return set;
}
// 根据便签ID获取电话号码的方法。
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e

@ -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.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;
// DateTimePicker 类继承自 FrameLayout用于提供一个日期时间选择器。
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小时制和12小时制下小时选择器的最小值和最大值。
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
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;
// 日期、小时、分钟和上下午的 NumberPicker 控件。
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
// 用于存储日期的 Calendar 对象。
private 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;
// 日期改变监听器。
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) {
// 更新小时并通知日期时间改变。
// 处理12小时制下上下午的转换。
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
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;
} 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;
}
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();
}
} else {
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);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
// 分钟改变监听器。
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;
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
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() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 更新上下午并通知日期时间改变。
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);
}
// DateTimePicker 的构造函数。
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
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);
// 初始化日期、小时、分钟和上下午的 NumberPicker 控件。
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);
//

@ -0,0 +1,98 @@
/*
* 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.util.Calendar; // 引入Calendar类用于处理日期和时间
import net.micode.notes.R; // 引入R文件用于获取资源ID
import net.micode.notes.ui.DateTimePicker; // 引入自定义的DateTimePicker控件
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; // 引入DateTimePicker控件的监听器
import android.app.AlertDialog; // 引入AlertDialog类用于创建对话框
import android.content.Context; // 引入Context类用于访问应用程序上下文
import android.content.DialogInterface; // 引入DialogInterface类用于处理对话框的按钮事件
import android.content.DialogInterface.OnClickListener; // 引入OnClickListener接口用于按钮点击事件
import android.text.format.DateFormat; // 引入DateFormat类用于格式化日期和时间
import android.text.format.DateUtils; // 引入DateUtils类用于时间格式化与操作
// 定义一个自定义的DateTimePickerDialog类继承自AlertDialog
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance(); // 用于保存当前日期和时间
private boolean mIs24HourView; // 标记是否为24小时制显示
private OnDateTimeSetListener mOnDateTimeSetListener; // 回调接口,用于在日期和时间设置后通知调用者
private DateTimePicker mDateTimePicker; // 自定义的日期时间选择器
// 定义回调接口,用于在用户设置日期时间后进行处理
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date); // 回调方法,传递设置的日期时间
}
// 构造函数,初始化对话框,设置默认日期时间
public DateTimePickerDialog(Context context, long date) {
super(context); // 调用父类构造函数初始化AlertDialog
mDateTimePicker = new DateTimePicker(context); // 初始化DateTimePicker控件
setView(mDateTimePicker); // 设置对话框的内容视图为DateTimePicker控件
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { // 设置日期时间变化的监听器
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year); // 设置选中的年
mDate.set(Calendar.MONTH, month); // 设置选中的月
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); // 设置选中的天
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); // 设置选中的小时
mDate.set(Calendar.MINUTE, minute); // 设置选中的分钟
updateTitle(mDate.getTimeInMillis()); // 更新对话框标题显示的日期时间
}
});
mDate.setTimeInMillis(date); // 将传入的日期时间(毫秒)设置为当前日期
mDate.set(Calendar.SECOND, 0); // 将秒设置为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); // 设置DateTimePicker控件的初始日期
setButton(context.getString(R.string.datetime_dialog_ok), this); // 设置“确认”按钮,并指定点击时的行为
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); // 设置“取消”按钮
set24HourView(DateFormat.is24HourFormat(this.getContext())); // 设置是否为24小时制显示
updateTitle(mDate.getTimeInMillis()); // 初始化时更新对话框的标题
}
// 设置是否使用24小时制
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
// 设置日期时间设置完成后的回调监听器
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
// 更新对话框标题,显示当前选中的日期时间
private void updateTitle(long date) {
// 设置日期时间显示的格式
int flag =
DateUtils.FORMAT_SHOW_YEAR | // 显示年份
DateUtils.FORMAT_SHOW_DATE | // 显示日期
DateUtils.FORMAT_SHOW_TIME; // 显示时间
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; // 如果是24小时制显示24小时格式
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); // 格式化日期时间并设置为对话框标题
}
// 确认按钮点击事件的处理
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
// 调用回调接口,传递设置的日期时间
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,63 @@
/*
* 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 android.content.Context; // 引入Context类用于访问应用程序的上下文
import android.view.Menu; // 引入Menu类用于操作菜单
import android.view.MenuItem; // 引入MenuItem类用于菜单项的处理
import android.view.View; // 引入View类用于视图的操作
import android.view.View.OnClickListener; // 引入OnClickListener接口用于处理点击事件
import android.widget.Button; // 引入Button类用于显示按钮
import android.widget.PopupMenu; // 引入PopupMenu类用于显示弹出菜单
import android.widget.PopupMenu.OnMenuItemClickListener; // 引入PopupMenu的菜单项点击监听器
public class DropdownMenu {
private Button mButton; // 用于显示的按钮,点击后弹出菜单
private PopupMenu mPopupMenu; // 弹出菜单的实例
private Menu mMenu; // 菜单项的集合
// 构造函数初始化DropdownMenu设置按钮及其弹出菜单
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button; // 获取传入的按钮对象
mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮的背景图标为下拉菜单图标
mPopupMenu = new PopupMenu(context, mButton); // 初始化PopupMenu指定按钮作为弹出菜单的锚点
mMenu = mPopupMenu.getMenu(); // 获取PopupMenu中的Menu对象
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 使用传入的menuId加载菜单资源
mButton.setOnClickListener(new OnClickListener() { // 为按钮设置点击事件监听器
public void onClick(View v) {
mPopupMenu.show(); // 当按钮被点击时,显示弹出菜单
}
});
}
// 设置菜单项点击事件监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener); // 设置PopupMenu的菜单项点击监听器
}
}
// 根据菜单项的ID查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id); // 在菜单中查找具有指定ID的菜单项
}
// 设置按钮的文本
public void setTitle(CharSequence title) {
mButton.setText(title); // 设置按钮的文本为传入的title
}
}

@ -0,0 +1,89 @@
/*
* 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 android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
// FoldersListAdapter 是一个自定义的 CursorAdapter用于显示文件夹列表。
public class FoldersListAdapter extends CursorAdapter {
// 定义查询数据库时需要的列。
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
// 定义列索引常量。
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
// FoldersListAdapter 的构造函数。
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
// 新建视图的方法,返回一个 FolderListItem 实例。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
// 绑定视图与数据的方法。
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
// 根据位置获取文件夹名称的方法。
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
// FolderListItem 是一个内部类,表示列表中的单个文件夹项。
private class FolderListItem extends LinearLayout {
private TextView mName; // 用于显示文件夹名称的 TextView。
// FolderListItem 的构造函数。
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
// 绑定文件夹名称的方法。
public void bind(String name) {
mName.setText(name);
}
}
}

@ -0,0 +1,161 @@
/*
* 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; // 包声明,表示该类位于 net.micode.notes.tool 包中
// 定义 GTaskStringUtils 类,用于管理 Google Tasks 或类似任务相关的常量字符串
public class GTaskStringUtils {
// 这些常量字符串用于表示任务数据的各个字段名
// Action ID
public final static String GTASK_JSON_ACTION_ID = "action_id"; // "action_id" 用于表示任务的操作标识符
// 操作列表
public final static String GTASK_JSON_ACTION_LIST = "action_list"; // "action_list" 用于表示操作列表
// 操作类型
public final static String GTASK_JSON_ACTION_TYPE = "action_type"; // "action_type" 用于表示操作的类型
// 创建操作类型
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; // "create" 表示创建操作
// 获取所有操作类型
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; // "get_all" 表示获取所有操作
// 移动操作类型
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; // "move" 表示移动操作
// 更新操作类型
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; // "update" 表示更新操作
// 创建者ID
public final static String GTASK_JSON_CREATOR_ID = "creator_id"; // "creator_id" 表示任务创建者的ID
// 子实体
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; // "child_entity" 用于表示子实体
// 客户端版本
public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; // "client_version" 表示客户端的版本
// 任务是否完成
public final static String GTASK_JSON_COMPLETED = "completed"; // "completed" 表示任务是否完成
// 当前列表ID
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; // "current_list_id" 表示当前任务列表的ID
// 默认列表ID
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; // "default_list_id" 表示默认任务列表的ID
// 是否删除
public final static String GTASK_JSON_DELETED = "deleted"; // "deleted" 表示任务是否被删除
// 目标列表
public final static String GTASK_JSON_DEST_LIST = "dest_list"; // "dest_list" 表示目标任务列表
// 目标父级任务
public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; // "dest_parent" 表示目标任务的父级任务
// 目标父级类型
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; // "dest_parent_type" 表示目标父级任务的类型
// 实体增量
public final static String GTASK_JSON_ENTITY_DELTA = "entity"; // "entity" 表示实体数据的增量部分
// 实体类型
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; // "entity_type" 用于表示实体类型(任务、列表等)
// 获取已删除任务
public final static String GTASK_JSON_GET_DELETED = "get_deleted"; // "get_deleted" 表示获取已删除的任务
// 任务ID
public final static String GTASK_JSON_ID = "id"; // "id" 用于表示任务的唯一标识符
// 任务索引
public final static String GTASK_JSON_INDEX = "index"; // "index" 用于表示任务的索引位置
// 最后修改时间
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; // "last_modified" 用于表示任务的最后修改时间
// 最新同步点
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; // "latest_sync_point" 表示任务的最新同步点
// 列表ID
public final static String GTASK_JSON_LIST_ID = "list_id"; // "list_id" 用于表示任务列表的ID
// 列表字段
public final static String GTASK_JSON_LISTS = "lists"; // "lists" 用于表示多个任务列表
// 任务名称
public final static String GTASK_JSON_NAME = "name"; // "name" 表示任务或任务列表的名称
// 新的ID
public final static String GTASK_JSON_NEW_ID = "new_id"; // "new_id" 用于表示任务的新ID
// 任务备注
public final static String GTASK_JSON_NOTES = "notes"; // "notes" 表示任务的备注信息
// 父任务ID
public final static String GTASK_JSON_PARENT_ID = "parent_id"; // "parent_id" 表示父任务的ID
// 之前的兄弟任务ID
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; // "prior_sibling_id" 用于表示之前的兄弟任务ID
// 任务结果
public final static String GTASK_JSON_RESULTS = "results"; // "results" 用于表示任务操作的结果
// 源列表
public final static String GTASK_JSON_SOURCE_LIST = "source_list"; // "source_list" 用于表示源任务列表
// 任务列表
public final static String GTASK_JSON_TASKS = "tasks"; // "tasks" 用于表示任务数据
// 任务类型
public final static String GTASK_JSON_TYPE = "type"; // "type" 表示任务的类型
// 任务组类型
public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; // "GROUP" 表示任务组类型
// 任务类型
public final static String GTASK_JSON_TYPE_TASK = "TASK"; // "TASK" 表示单一任务类型
// 用户字段
public final static String GTASK_JSON_USER = "user"; // "user" 用于表示任务所属用户的信息
// MIUI 文件夹前缀
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; // "[MIUI_Notes]" 用于表示 MIUI 系统中的笔记文件夹前缀
// 默认文件夹
public final static String FOLDER_DEFAULT = "Default"; // "Default" 表示默认的文件夹名称
// 通话记录笔记文件夹
public final static String FOLDER_CALL_NOTE = "Call_Note"; // "Call_Note" 用于表示通话记录的笔记文件夹
// 元数据文件夹
public final static String FOLDER_META = "METADATA"; // "METADATA" 用于表示存储元数据的文件夹
// 元数据头部: GTASK ID
public final static String META_HEAD_GTASK_ID = "meta_gid"; // "meta_gid" 用于表示任务的 Google Task ID 的元数据字段
// 元数据头部: 笔记
public final static String META_HEAD_NOTE = "meta_note"; // "meta_note" 用于表示与任务相关的元数据笔记字段
// 元数据头部: 数据
public final static String META_HEAD_DATA = "meta_data"; // "meta_data" 用于表示与任务相关的其他元数据字段
// 元数据笔记名称
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; // "[META INFO] DON'T UPDATE AND DELETE" 是一条标记元数据的警告信息,表示不要更新或删除该元数据
}

@ -0,0 +1,515 @@
/*
* 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 android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// NoteEditActivity 是一个用于编辑便签的 Activity。
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
// 内部类,用于持有头部视图的控件引用。
private class HeadViewHolder {
public TextView tvModified; // 修改日期的 TextView。
public ImageView ivAlertIcon; // 提醒图标的 ImageView。
public TextView tvAlertDate; // 提醒日期的 TextView。
public ImageView ibSetBgColor; // 设置背景颜色的 ImageView。
}
// 背景颜色选择按钮与颜色 ID 的映射。
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
// 背景颜色选择结果与按钮 ID 的映射。
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
// 字体大小选择按钮与大小 ID 的映射。
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
// 字体大小选择结果与按钮 ID 的映射。
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
// 日志标签。
private static final String TAG = "NoteEditActivity";
// 头部视图的 Holder。
private HeadViewHolder mNoteHeaderHolder;
// 头部视图面板、背景颜色选择器和字体大小选择器。
private View mHeadViewPanel;
private View mNoteBgColorSelector;
private View mFontSizeSelector;
// 编辑器和编辑器面板。
private EditText mNoteEditor;
private View mNoteEditorPanel;
// 正在编辑的便签。
private WorkingNote mWorkingNote;
// SharedPreferences 和字体大小 ID。
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
// SharedPreferences 中字体大小的键。
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
// 快捷方式图标标题的最大长度。
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
// 多选框标记。
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
// 编辑文本列表。
private LinearLayout mEditTextList;
// 用户查询字符串和正则表达式模式。
private String mUserQuery;
private Pattern mPattern;
// onCreate 方法,初始化 Activity。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
}
// onRestoreInstanceState 方法,恢复 Activity 状态。
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
// initActivityState 方法,初始化 Activity 状态。
private boolean initActivityState(Intent intent) {
// 省略部分代码...
}
// onResume 方法,恢复 Activity 时调用。
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}
// initNoteScreen 方法,初始化便签屏幕。
private void initNoteScreen() {
// 省略部分代码...
}
// onNewIntent 方法,处理新的 Intent。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
// onSaveInstanceState 方法,保存 Activity 状态。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 省略部分代码...
}
// dispatchTouchEvent 方法,分发触摸事件。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略部分代码...
}
// inRangeOfView 方法,判断触摸事件是否在视图范围内。
private boolean inRangeOfView(View view, MotionEvent ev) {
// 省略部分代码...
}
// initResources 方法,初始化资源。
private void initResources() {
// 省略部分代码...
}
// onPause 方法,暂停 Activity 时调用。
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
// updateWidget 方法,更新小部件。
private void updateWidget() {
// 省略部分代码...
}
}
/**
*
* @param v
*/
public void onClick(View v) {
int id = v.getId(); // 获取被点击视图的ID。
// 如果被点击的视图是设置背景颜色的按钮,则显示背景颜色选择器。
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(-View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) { // 如果被点击的是背景颜色按钮。
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); // 更改背景颜色ID。
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) { // 如果被点击的是字体大小按钮。
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id); // 更改字体大小ID。
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); // 保存新的字体大小偏好。
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
// 如果便签处于清单模式,更新清单视图;否则,更新文本外观。
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
}
/**
*
*/
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
/**
*
* @return truefalse
*/
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
/**
*
*/
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
/**
*
* @param menu
* @return truefalse
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
// 根据便签的文件夹ID决定加载哪个菜单。
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
// 根据便签的属性更新菜单项。
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}
/**
*
* @param item
* @return truefalse
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote();
break;
case R.id.menu_delete:
// 显示删除便签的确认对话框。
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop:
sendToDesktop();
break;
case R.id.menu_alert:
setReminder();
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
default:
break;
}
return true;
}
/**
* 便
*/
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date, true);
}
});
d.show();
}
/**
* 便{@link Intent#ACTION_SEND}{text/plain}
* @param context
* @param info 便
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}
/**
* 便
*/
private void createNewNote() {
// 在创建新便签之前保存当前正在编辑的便签。
saveNote();
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
/**
* 便
*/
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "错误的便签ID不应该发生");
}
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "删除便签错误");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "将便签移动到废纸篓文件夹错误,不应该发生");
}
}
}
mWorkingNote.markDeleted(true);
}
/**
*
* @return truefalse
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
*
* @param date
* @param set truefalse
*/
public void onClockAlertChanged(long date, boolean set) {
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
Log.e(TAG, "时钟提醒设置错误");
showToast(R.string.error_note_empty_for_clock);
}
}
/**
*
*/
public void onWidgetChanged() {
updateWidget();
}
/**
*
* @param index
* @param text
*/

@ -0,0 +1,304 @@
/*
* 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 android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
/*
* 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; // 定义类所在的包路径
// 引入相关的 Android 类
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R; // 引入资源文件中的R类
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText"; // 定义日志标签,用于调试输出
private int mIndex; // 用于表示当前编辑文本的索引
private int mSelectionStartBeforeDelete; // 用于记录删除前的文本选择开始位置
// 定义常见的URI Schemes
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
// 定义一个映射关系存储不同URI Scheme对应的资源ID
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话链接对应的资源ID
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // 网站链接对应的资源ID
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 邮件链接对应的资源ID
}
/**
* {@link NoteEditActivity}
*/
public interface OnTextViewChangeListener {
/**
*
*/
void onEditTextDelete(int index, String text);
/**
*
*/
void onEditTextEnter(int index, String text);
/**
*
*/
void onTextChange(int index, boolean hasText);
}
}
// 定义一个文本视图变化监听器的接口
private OnTextViewChangeListener mOnTextViewChangeListener;
// NoteEditText的构造函数当从AttributeSet创建时调用
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
// 设置当前文本编辑框的索引
public void setIndex(int index) {
mIndex = index;
}
// 设置文本视图变化监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// NoteEditText的构造函数当从AttributeSet和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// NoteEditText的构造函数当从AttributeSet、defStyle和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
// 处理触摸事件,用于实现自定义的文本选择行为
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event);
}
// 处理按键事件例如Enter和Delete键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
// 处理按键释放事件例如Enter和Delete键
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
// 处理焦点变化事件,用于在文本编辑框失去焦点时通知监听器
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
// 创建上下文菜单,用于实现长按文本时弹出的菜单
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}

@ -0,0 +1,156 @@
// 定义一个文本视图变化监听器的接口,用于监听文本编辑框中的变化事件
private OnTextViewChangeListener mOnTextViewChangeListener;
// NoteEditText的构造函数当从AttributeSet创建时调用
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
// 设置当前文本编辑框的索引,用于标识编辑框的位置
public void setIndex(int index) {
mIndex = index;
}
// 设置文本视图变化监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// NoteEditText的构造函数当从AttributeSet和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// NoteEditText的构造函数当从AttributeSet、defStyle和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
// 处理触摸事件,用于实现自定义的文本选择行为
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算触摸点在文本中的位置,并设置光标
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event);
}
// 处理按键事件例如Enter和Delete键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 如果设置了监听器消费Enter键事件
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 在删除之前记录光标的位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
// 处理按键释放事件例如Enter和Delete键
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
// 如果设置了监听器,并且在删除之前光标位于文本开头,则通知监听器删除事件
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
// 如果设置了监听器通知监听器Enter键事件创建新的文本行
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
// 处理焦点变化事件,用于在文本编辑框失去焦点时通知监听器
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
// 如果文本编辑框失去焦点且文本为空,通知监听器文本变化
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
// 创建上下文菜单,用于实现长按文本时弹出的菜单
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 如果选中的文本包含URL添加一个菜单项用于打开链接
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 打开链接
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}

@ -0,0 +1,55 @@
/*
* 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 的具体实现类用于提供2x2网格大小的便签小部件。
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 当小部件需要更新时调用此方法。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的 update 方法来执行更新。
super.update(context, appWidgetManager, appWidgetIds);
}
// 返回小部件的布局资源ID。
@Override
protected int getLayoutId() {
// 返回2x2网格小部件的布局资源ID。
return R.layout.widget_2x;
}
// 根据传入的背景ID返回对应的资源ID。
@Override
protected int getBgResourceId(int bgId) {
// 使用 ResourceParser 类的静态方法来获取2x2网格小部件的背景资源ID。
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
// 返回小部件的类型。
@Override
protected int getWidgetType() {
// 返回小部件的类型为TYPE_WIDGET_2X这是一个定义在Notes类中的常量。
return Notes.TYPE_WIDGET_2X;
}
}

@ -0,0 +1,55 @@
/*
* 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_4x 类继承自 NoteWidgetProvider 类用于提供4x4网格大小的便签小部件。
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
// 当小部件需要更新时,系统会调用此方法。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的 update 方法来更新小部件。
super.update(context, appWidgetManager, appWidgetIds);
}
// 返回小部件的布局资源ID。
@Override
protected int getLayoutId() {
// 返回4x4网格小部件的布局资源ID。
return R.layout.widget_4x;
}
// 根据传入的背景ID返回对应的资源ID。
@Override
protected int getBgResourceId(int bgId) {
// 使用 ResourceParser 类的静态方法来获取4x4网格小部件的背景资源ID。
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
// 返回小部件的类型。
@Override
protected int getWidgetType() {
// 返回小部件的类型为TYPE_WIDGET_4X这是一个定义在Notes类中的常量。
return Notes.TYPE_WIDGET_4X;
}
}

@ -0,0 +1,368 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
// NotesListActivity 是一个用于显示便签列表的 Activity。
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// 定义查询令牌,用于处理数据库查询。
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
// 定义菜单项的常量。
private static final int MENU_FOLDER_DELETE = 0;
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 定义 SharedPreferences 中保存的“添加介绍”的键。
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
// 枚举类,表示当前列表的编辑状态。
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
};
private ListEditState mState;
// 后台查询处理器。
private BackgroundQueryHandler mBackgroundQueryHandler;
// 便签列表适配器。
private NotesListAdapter mNotesListAdapter;
// 便签列表视图。
private ListView mNotesListView;
// 添加新便签的按钮。
private Button mAddNewNote;
// 处理触摸事件的变量。
private boolean mDispatch;
private int mOriginY;
private int mDispatchY;
// 标题栏。
private TextView mTitleBar;
// 当前文件夹ID。
private long mCurrentFolderId;
// 内容解析器。
private ContentResolver mContentResolver;
// 模式回调。
private ModeCallback mModeCallBack;
// 日志标签。
private static final String TAG = "NotesListActivity";
// 便签列表视图滚动速率。
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
// 焦点便签数据项。
private NoteItemData mFocusNoteDataItem;
// 普通选择和根文件夹选择的SQL语句。
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
// 请求代码常量。
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
// onCreate 方法,初始化 Activity。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list);
initResources();
/**
* 使
*/
setAppInfoFromRawRes();
}
// onActivityResult 方法,处理 Activity 结果。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
mNotesListAdapter.changeCursor(null);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
// 从 raw 资源文件中设置应用程序信息。
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
in = getResources().openRawResource(R.raw.introduction);
if (in != null) {
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
char [] buf = new char[1024];
int len = 0;
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
note.setWorkingText(sb.toString());
if (note.saveNote()) {
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
Log.e(TAG, "Save introduction note error");
return;
}
}
}
}
/**
* Activity
*/
@Override
protected void onStart() {
super.onStart(); // 调用父类的 onStart 方法。
startAsyncNotesListQuery(); // 开始异步查询便签列表。
}
/**
*
*/
private void initResources() {
mContentResolver = this.getContentResolver(); // 获取内容解析器。
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); // 创建后台查询处理器。
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹。
mNotesListView = (ListView) findViewById(R.id.notes_list); // 获取便签列表视图。
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); // 添加列表底部视图。
mNotesListView.setOnItemClickListener(new OnListItemClickListener()); // 设置列表项点击事件监听器。
mNotesListView.setOnItemLongClickListener(this); // 设置列表项长按事件监听器。
mNotesListAdapter = new NotesListAdapter(this); // 创建便签列表适配器。
mNotesListView.setAdapter(mNotesListAdapter); // 设置列表适配器。
mAddNewNote = (Button) findViewById(R.id.btn_new_note); // 获取添加新便签的按钮。
mAddNewNote.setOnClickListener(this); // 设置按钮点击事件监听器。
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 设置按钮触摸事件监听器。
mDispatch = false; // 初始化 dispatch 变量。
mDispatchY = 0; // 初始化 dispatchY 变量。
mOriginY = 0; // 初始化 originY 变量。
mTitleBar = (TextView) findViewById(R.id.tv_title_bar); // 获取标题栏。
mState = ListEditState.NOTE_LIST; // 初始化列表编辑状态。
mModeCallBack = new ModeCallback(); // 创建模式回调对象。
}
/**
* ModeCallback ListView
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu; // 下拉菜单。
private ActionMode mActionMode; // 动作模式。
private MenuItem mMoveMenu; // 移动菜单项。
/**
*
* @param mode
* @param menu
* @return
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 省略部分代码...
}
/**
*
*/
private void updateMenu() {
// 省略部分代码...
}
/**
*
* @param mode
* @param menu
* @return
*/
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
/**
*
* @param mode
* @param item
* @return
*/
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
/**
*
* @param mode
*/
public void onDestroyActionMode(ActionMode mode) {
// 省略部分代码...
}
/**
*
*/
public void finishActionMode() {
mActionMode.finish();
}
/**
*
* @param mode
* @param position
* @param id ID
* @param checked
*/
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
// 省略部分代码...
}
/**
*
* @param item
* @return
*/
public boolean onMenuItemClick(MenuItem item) {
// 省略部分代码...
}
}
/**
* NewNoteOnTouchListener 便
*/
private class NewNoteOnTouchListener implements OnTouchListener {
/**
*
* @param v
* @param event
* @return
*/
public boolean onTouch(View v, MotionEvent event) {
// 省略部分代码...
}
}
/**
* 便
*/
private void startAsyncNotesListQuery() {
// 省略部分代码...
}
/**
* BackgroundQueryHandler AsyncQueryHandler
*/
private final class BackgroundQueryHandler extends AsyncQueryHandler {
/**
*
* @param contentResolver
*/
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
/**
*
* @param token
* @param cookie
* @param cursor
*/
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// 省略部分代码...
}
}

@ -0,0 +1,196 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
// NotesListAdapter 是一个继承自 CursorAdapter 的类,用于为便签列表提供数据和视图。
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter"; // 日志标签。
private Context mContext; // 上下文对象。
private HashMap<Integer, Boolean> mSelectedIndex; // 存储选中项的哈希映射。
private int mNotesCount; // 便签数量。
private boolean mChoiceMode; // 是否处于选择模式。
// AppWidgetAttribute 内部类,用于存储小部件的属性。
public static class AppWidgetAttribute {
public int widgetId; // 小部件ID。
public int widgetType; // 小部件类型。
};
// NotesListAdapter 的构造函数。
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
// 新建视图的方法。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
// 绑定视图和数据的方法。
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
// 设置项的选中状态。
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
// 获取是否处于选择模式。
public boolean isInChoiceMode() {
return mChoiceMode;
}
// 设置选择模式。
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
// 全选或全不选。
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
}
// 获取选中项的ID集合。
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
// 获取选中的小部件属性集合。
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
// 获取选中项的数量。
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
// 判断是否全部选中。
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
// 判断项是否被选中。
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
// 当内容改变时调用。
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
// 当游标改变时调用。
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
// 计算便签数量。
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -0,0 +1,127 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
// NotesListItem 类继承自LinearLayout用于显示便签列表中的单个项目。
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 用于显示提醒图标的ImageView。
private TextView mTitle; // 显示便签标题的TextView。
private TextView mTime; // 显示便签修改时间的TextView。
private TextView mCallName; // 显示通话记录姓名的TextView。
private NoteItemData mItemData; // 绑定到此列表项的便签数据。
private CheckBox mCheckBox; // 用于选择模式的CheckBox。
// NotesListItem的构造函数初始化视图和组件。
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
// 绑定便签数据到列表项。
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
// 根据便签数据的类型和属性设置视图的显示。
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setBackground(data);
}
// 设置背景色的方法。
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
// 根据便签的类型和位置设置不同的背景。
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
// 获取绑定到此列表项的便签数据。
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,266 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
// NotesPreferenceActivity 是一个用于设置便签应用偏好的 Activity。
public class NotesPreferenceActivity extends PreferenceActivity {
// 偏好设置文件的名称。
public static final String PREFERENCE_NAME = "notes_preferences";
// 同步账户名称的键。
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
// 最后同步时间的键。
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
// 设置背景颜色的键。
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
// 同步账户的键。
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
// 权限过滤键。
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory; // 账户类别的 PreferenceCategory。
private GTaskReceiver mReceiver; // 用于接收 GTask 同步状态的 BroadcastReceiver。
private Account[] mOriAccounts; // 原始账户数组。
private boolean mHasAddedAccount; // 是否添加了账户。
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 使用应用图标作为导航。
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从 XML 文件中添加偏好设置。
addPreferencesFromResource(R.xml.preferences);
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
mOriAccounts = null;
// 从布局文件中添加设置头部。
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
@Override
protected void onResume() {
super.onResume();
// 自动设置同步账户,如果用户添加了新账户。
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
}
refreshUI();
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
// 加载账户偏好设置。
private void loadAccountPreference() {
mAccountCategory.removeAll();
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// 第一次设置账户。
showSelectAccountAlertDialog();
} else {
// 如果已经设置了账户,需要提示用户风险。
showChangeAccountConfirmAlertDialog();
}
} else {
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
mAccountCategory.addPreference(accountPref);
}
// 加载同步按钮。
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 设置按钮状态。
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 设置最后同步时间。
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
// 刷新 UI。
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
// 显示选择账户的对话框。
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
items[index++] = account.name;
}
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI();
}
});
}
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
startActivityForResult(intent, -1);
dialog.dismiss();
}
});
}
// 显示更改账户确认对话框。
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string

@ -0,0 +1,275 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
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 java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
// BackupUtils 类提供备份和恢复便签数据的功能。
public class BackupUtils {
private static final String TAG = "BackupUtils";
// 单例模式
private static BackupUtils sInstance;
// 获取 BackupUtils 单例对象
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
*
*/
// 当前 sdcard 没有挂载
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 备份文件不存在
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 数据格式不正确,可能被其他程序修改
public static final int STATE_DATA_DESTROIED = 2;
// 一些运行时异常导致恢复或备份失败
public static final int STATE_SYSTEM_ERROR = 3;
// 备份或恢复成功
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
// BackupUtils 构造函数
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
// 检查外部存储是否可用
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 导出数据为文本
public int exportToText() {
return mTextExport.exportToText();
}
// 获取导出的文本文件名
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
// 获取导出的文本文件目录
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
// TextExport 内部类,处理文本导出
private static class TextExport {
// 省略部分代码...
// TextExport 构造函数
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
// 获取格式化字符串
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
// 导出文件夹到文本
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询属于此文件夹的便签
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// 打印便签的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于此便签的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
// 导出便签到打印流
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询属于此便签的数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// 打印电话号码
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// 打印通话日期
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// 打印通话附件位置
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// 在便签之间打印分隔线
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
// 导出文本,用户可读
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 首先导出文件夹及其便签
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// 打印文件夹名称
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// 导出根文件夹中的便签
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于此便签的数据
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
// 获取指向导出文本文件的打印流
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();}
return null;
}
}

@ -0,0 +1,245 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
// DataUtils 类提供了一系列用于操作便签数据的工具方法。
public class DataUtils {
public static final String TAG = "DataUtils";
// 批量删除便签的方法。
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
// 将便签移动到指定文件夹的方法。
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
// 批量将便签移动到指定文件夹的方法。
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
// 获取用户文件夹数量的方法。
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close();
}
}
}
return count;
}
// 检查便签在数据库中是否存在的方法。
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 检查便签在数据库中是否存在的方法。
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 检查数据在数据库中是否存在的方法。
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 检查文件夹名称是否可见的方法。
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
// 获取文件夹便签小部件属性的方法。
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>();
do {
try {
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
set.add(widget);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
c.close();
}
return set;
}
// 根据便签ID获取电话号码的方法。
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e

@ -0,0 +1,161 @@
/*
* 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; // 包声明,表示该类位于 net.micode.notes.tool 包中
// 定义 GTaskStringUtils 类,用于管理 Google Tasks 或类似任务相关的常量字符串
public class GTaskStringUtils {
// 这些常量字符串用于表示任务数据的各个字段名
// Action ID
public final static String GTASK_JSON_ACTION_ID = "action_id"; // "action_id" 用于表示任务的操作标识符
// 操作列表
public final static String GTASK_JSON_ACTION_LIST = "action_list"; // "action_list" 用于表示操作列表
// 操作类型
public final static String GTASK_JSON_ACTION_TYPE = "action_type"; // "action_type" 用于表示操作的类型
// 创建操作类型
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; // "create" 表示创建操作
// 获取所有操作类型
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; // "get_all" 表示获取所有操作
// 移动操作类型
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; // "move" 表示移动操作
// 更新操作类型
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; // "update" 表示更新操作
// 创建者ID
public final static String GTASK_JSON_CREATOR_ID = "creator_id"; // "creator_id" 表示任务创建者的ID
// 子实体
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; // "child_entity" 用于表示子实体
// 客户端版本
public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; // "client_version" 表示客户端的版本
// 任务是否完成
public final static String GTASK_JSON_COMPLETED = "completed"; // "completed" 表示任务是否完成
// 当前列表ID
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; // "current_list_id" 表示当前任务列表的ID
// 默认列表ID
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; // "default_list_id" 表示默认任务列表的ID
// 是否删除
public final static String GTASK_JSON_DELETED = "deleted"; // "deleted" 表示任务是否被删除
// 目标列表
public final static String GTASK_JSON_DEST_LIST = "dest_list"; // "dest_list" 表示目标任务列表
// 目标父级任务
public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; // "dest_parent" 表示目标任务的父级任务
// 目标父级类型
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; // "dest_parent_type" 表示目标父级任务的类型
// 实体增量
public final static String GTASK_JSON_ENTITY_DELTA = "entity"; // "entity" 表示实体数据的增量部分
// 实体类型
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; // "entity_type" 用于表示实体类型(任务、列表等)
// 获取已删除任务
public final static String GTASK_JSON_GET_DELETED = "get_deleted"; // "get_deleted" 表示获取已删除的任务
// 任务ID
public final static String GTASK_JSON_ID = "id"; // "id" 用于表示任务的唯一标识符
// 任务索引
public final static String GTASK_JSON_INDEX = "index"; // "index" 用于表示任务的索引位置
// 最后修改时间
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; // "last_modified" 用于表示任务的最后修改时间
// 最新同步点
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; // "latest_sync_point" 表示任务的最新同步点
// 列表ID
public final static String GTASK_JSON_LIST_ID = "list_id"; // "list_id" 用于表示任务列表的ID
// 列表字段
public final static String GTASK_JSON_LISTS = "lists"; // "lists" 用于表示多个任务列表
// 任务名称
public final static String GTASK_JSON_NAME = "name"; // "name" 表示任务或任务列表的名称
// 新的ID
public final static String GTASK_JSON_NEW_ID = "new_id"; // "new_id" 用于表示任务的新ID
// 任务备注
public final static String GTASK_JSON_NOTES = "notes"; // "notes" 表示任务的备注信息
// 父任务ID
public final static String GTASK_JSON_PARENT_ID = "parent_id"; // "parent_id" 表示父任务的ID
// 之前的兄弟任务ID
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; // "prior_sibling_id" 用于表示之前的兄弟任务ID
// 任务结果
public final static String GTASK_JSON_RESULTS = "results"; // "results" 用于表示任务操作的结果
// 源列表
public final static String GTASK_JSON_SOURCE_LIST = "source_list"; // "source_list" 用于表示源任务列表
// 任务列表
public final static String GTASK_JSON_TASKS = "tasks"; // "tasks" 用于表示任务数据
// 任务类型
public final static String GTASK_JSON_TYPE = "type"; // "type" 表示任务的类型
// 任务组类型
public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; // "GROUP" 表示任务组类型
// 任务类型
public final static String GTASK_JSON_TYPE_TASK = "TASK"; // "TASK" 表示单一任务类型
// 用户字段
public final static String GTASK_JSON_USER = "user"; // "user" 用于表示任务所属用户的信息
// MIUI 文件夹前缀
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; // "[MIUI_Notes]" 用于表示 MIUI 系统中的笔记文件夹前缀
// 默认文件夹
public final static String FOLDER_DEFAULT = "Default"; // "Default" 表示默认的文件夹名称
// 通话记录笔记文件夹
public final static String FOLDER_CALL_NOTE = "Call_Note"; // "Call_Note" 用于表示通话记录的笔记文件夹
// 元数据文件夹
public final static String FOLDER_META = "METADATA"; // "METADATA" 用于表示存储元数据的文件夹
// 元数据头部: GTASK ID
public final static String META_HEAD_GTASK_ID = "meta_gid"; // "meta_gid" 用于表示任务的 Google Task ID 的元数据字段
// 元数据头部: 笔记
public final static String META_HEAD_NOTE = "meta_note"; // "meta_note" 用于表示与任务相关的元数据笔记字段
// 元数据头部: 数据
public final static String META_HEAD_DATA = "meta_data"; // "meta_data" 用于表示与任务相关的其他元数据字段
// 元数据笔记名称
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; // "[META INFO] DON'T UPDATE AND DELETE" 是一条标记元数据的警告信息,表示不要更新或删除该元数据
}

@ -0,0 +1,208 @@
/*
* (c) 2010-2011, MiCode (www.micode.net)
*
* Apache License, Version 2.0 ()使
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.tool;
import android.content.Context; // 引入Android上下文类用于访问应用的资源和类以及调用应用级操作
import android.preference.PreferenceManager; // 引入Android偏好设置管理器类用于访问应用的偏好设置
import net.micode.notes.R; // 引入应用的资源类,用于访问应用资源
import net.micode.notes.ui.NotesPreferenceActivity; // 引入应用的偏好设置活动类
// ResourceParser类用于解析和管理应用的资源
public class ResourceParser {
// 定义一些颜色常量
public static final int YELLOW = 0; // 黄色
public static final int BLUE = 1; // 蓝色
public static final int WHITE = 2; // 白色
public static final int GREEN = 3; // 绿色
public static final int RED = 4; // 红色
// 定义默认背景颜色为黄色
public static final int BG_DEFAULT_COLOR = YELLOW;
// 定义一些字体大小常量
public static final int TEXT_SMALL = 0; // 小号字体
public static final int TEXT_MEDIUM = 1; // 中号字体
public static final int TEXT_LARGE = 2; // 大号字体
public static final int TEXT_SUPER = 3; // 超大号字体
// 定义默认字体大小为中号
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
// NoteBgResources内部类用于管理笔记背景资源
public static class NoteBgResources {
// 定义编辑状态下的背景资源数组
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
};
// 定义编辑状态下标题的背景资源数组
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的编辑背景资源
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
// 获取指定ID的编辑标题背景资源
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
// 获取默认背景颜色ID
public static int getDefaultBgId(Context context) {
// 检查用户是否设置了背景颜色偏好
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 如果设置了则随机返回一个背景颜色ID
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
// 否则返回默认背景颜色ID
return BG_DEFAULT_COLOR;
}
}
// NoteItemBgResources内部类用于管理笔记条目背景资源
public static class NoteItemBgResources {
// 定义列表第一项的背景资源数组
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
};
// 定义列表中间项的背景资源数组
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
};
// 定义列表最后一项的背景资源数组
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,
};
// 定义单个列表项的背景资源数组
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的第一项背景资源
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
// 获取指定ID的最后一项背景资源
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
// 获取指定ID的单个项背景资源
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
// 获取指定ID的中间项背景资源
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
// 获取文件夹背景资源
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
// WidgetBgResources内部类用于管理小部件背景资源
public static class WidgetBgResources {
// 定义2x大小的小部件背景资源数组
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大小小部件背景资源
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
// 定义4x大小的小部件背景资源数组
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大小小部件背景资源
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
// TextAppearanceResources内部类用于管理文本外观资源
public static class TextAppearanceResources {
// 定义文本外观资源数组
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
// 获取指定ID的文本外观资源
// 注意这里有一个修正HACKME用于处理存储在偏好设置中的资源ID可能超出资源数组长度的情况
public static int getTexAppearanceResource(int id) {
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,159 @@
/*
* 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 android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
// AlarmAlertActivity 是一个用于显示便签提醒的 Activity。
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId; // 便签的ID。
private String mSnippet; // 便签的预览文本。
private static final int SNIPPET_PREW_MAX_LEN = 60; // 预览文本的最大长度。
MediaPlayer mPlayer; // 用于播放提醒声音的 MediaPlayer。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求无标题栏的窗口特性。
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 锁屏时显示窗口。
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); // 屏幕关闭时点亮屏幕。
}
Intent intent = getIntent(); // 获取启动该 Activity 的 Intent。
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 从 Intent 中获取便签 ID。
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 获取便签预览文本。
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet; // 预览文本超出最大长度时截断。
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer(); // 初始化 MediaPlayer。
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog(); // 显示操作对话框。
playAlarmSound(); // 播放提醒声音。
} else {
finish(); // 如果便签不可见,则结束 Activity。
}
}
// 检查屏幕是否开启。
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
// 播放提醒声音。
private void playAlarmSound() {
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 显示操作对话框。
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
dialog.show().setOnDismissListener(this);
}
// DialogInterface.OnClickListener 的实现,处理对话框按钮点击事件。
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
break;
}
}
// DialogInterface.OnDismissListener 的实现,处理对话框消失事件。
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
// 停止提醒声音。
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}

@ -0,0 +1,75 @@
/*
* 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 android.app.AlarmManager; // 引入AlarmManager类用于设置闹钟
import android.app.PendingIntent; // 引入PendingIntent类表示延迟的Intent
import android.content.BroadcastReceiver; // 引入BroadcastReceiver类用于接收广播
import android.content.ContentUris; // 引入ContentUris类用于生成URI
import android.content.Context; // 引入Context类用于访问系统资源
import android.content.Intent; // 引入Intent类用于启动组件
import android.database.Cursor; // 引入Cursor类用于数据库查询
import net.micode.notes.data.Notes; // 引入Notes类处理笔记数据
import net.micode.notes.data.Notes.NoteColumns; // 引入NoteColumns类表示Notes表的列
public class AlarmInitReceiver extends BroadcastReceiver { // 定义一个继承自BroadcastReceiver的类
// 定义查询需要的列
private static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 获取笔记的ID列
NoteColumns.ALERTED_DATE // 获取提醒时间列
};
// 列索引常量
private static final int COLUMN_ID = 0; // 第0列是ID列
private static final int COLUMN_ALERTED_DATE = 1; // 第1列是提醒时间列
@Override
public void onReceive(Context context, Intent intent) { // 重写onReceive方法接收广播
long currentDate = System.currentTimeMillis(); // 获取当前的系统时间(以毫秒为单位)
// 查询数据库,获取需要提醒的笔记,且提醒时间大于当前时间
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, // 查询URI为Notes.CONTENT_NOTE_URI的内容提供者
PROJECTION, // 需要查询的列
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, // 查询条件:提醒时间大于当前时间,且类型为笔记
new String[] { String.valueOf(currentDate) }, // 查询条件中的当前时间
null); // 不需要排序
// 如果查询结果不为空
if (c != null) {
// 如果查询到至少一条记录
if (c.moveToFirst()) {
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE); // 获取提醒时间
Intent sender = new Intent(context, AlarmReceiver.class); // 创建一个新的Intent用于发送广播
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); // 设置Intent的数据笔记的ID
// 创建PendingIntent表示在指定时间执行AlarmReceiver广播
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取系统的AlarmManager服务
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// 设置闹钟使用RTC_WAKEUP模式当到达指定的提醒时间时唤醒设备并触发PendingIntent
alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext()); // 如果查询结果中还有下一条记录,则继续处理
}
c.close(); // 关闭Cursor
}
}
}

@ -0,0 +1,35 @@
/*
* 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
// AlarmReceiver 类继承自 BroadcastReceiver用于接收便签提醒的广播事件。
public class AlarmReceiver extends BroadcastReceiver {
// onReceive 方法是 BroadcastReceiver 的回调方法,当接收到广播时被调用。
@Override
public void onReceive(Context context, Intent intent) {
// 设置 intent 的目标类为 AlarmAlertActivity。
intent.setClass(context, AlarmAlertActivity.class);
// 为 intent 添加 FLAG_ACTIVITY_NEW_TASK 标志,使得在新的任务栈中启动 Activity。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 在给定的上下文中启动 AlarmAlertActivity。
context.startActivity(intent);
}
}

@ -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.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;
// DateTimePicker 类继承自 FrameLayout用于提供一个日期时间选择器。
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小时制和12小时制下小时选择器的最小值和最大值。
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
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;
// 日期、小时、分钟和上下午的 NumberPicker 控件。
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
// 用于存储日期的 Calendar 对象。
private 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;
// 日期改变监听器。
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) {
// 更新小时并通知日期时间改变。
// 处理12小时制下上下午的转换。
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
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;
} 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;
}
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();
}
} else {
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);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
// 分钟改变监听器。
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;
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
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() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 更新上下午并通知日期时间改变。
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);
}
// DateTimePicker 的构造函数。
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
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);
// 初始化日期、小时、分钟和上下午的 NumberPicker 控件。
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);
//

@ -0,0 +1,98 @@
/*
* 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.util.Calendar; // 引入Calendar类用于处理日期和时间
import net.micode.notes.R; // 引入R文件用于获取资源ID
import net.micode.notes.ui.DateTimePicker; // 引入自定义的DateTimePicker控件
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; // 引入DateTimePicker控件的监听器
import android.app.AlertDialog; // 引入AlertDialog类用于创建对话框
import android.content.Context; // 引入Context类用于访问应用程序上下文
import android.content.DialogInterface; // 引入DialogInterface类用于处理对话框的按钮事件
import android.content.DialogInterface.OnClickListener; // 引入OnClickListener接口用于按钮点击事件
import android.text.format.DateFormat; // 引入DateFormat类用于格式化日期和时间
import android.text.format.DateUtils; // 引入DateUtils类用于时间格式化与操作
// 定义一个自定义的DateTimePickerDialog类继承自AlertDialog
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance(); // 用于保存当前日期和时间
private boolean mIs24HourView; // 标记是否为24小时制显示
private OnDateTimeSetListener mOnDateTimeSetListener; // 回调接口,用于在日期和时间设置后通知调用者
private DateTimePicker mDateTimePicker; // 自定义的日期时间选择器
// 定义回调接口,用于在用户设置日期时间后进行处理
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date); // 回调方法,传递设置的日期时间
}
// 构造函数,初始化对话框,设置默认日期时间
public DateTimePickerDialog(Context context, long date) {
super(context); // 调用父类构造函数初始化AlertDialog
mDateTimePicker = new DateTimePicker(context); // 初始化DateTimePicker控件
setView(mDateTimePicker); // 设置对话框的内容视图为DateTimePicker控件
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { // 设置日期时间变化的监听器
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year); // 设置选中的年
mDate.set(Calendar.MONTH, month); // 设置选中的月
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); // 设置选中的天
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); // 设置选中的小时
mDate.set(Calendar.MINUTE, minute); // 设置选中的分钟
updateTitle(mDate.getTimeInMillis()); // 更新对话框标题显示的日期时间
}
});
mDate.setTimeInMillis(date); // 将传入的日期时间(毫秒)设置为当前日期
mDate.set(Calendar.SECOND, 0); // 将秒设置为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); // 设置DateTimePicker控件的初始日期
setButton(context.getString(R.string.datetime_dialog_ok), this); // 设置“确认”按钮,并指定点击时的行为
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); // 设置“取消”按钮
set24HourView(DateFormat.is24HourFormat(this.getContext())); // 设置是否为24小时制显示
updateTitle(mDate.getTimeInMillis()); // 初始化时更新对话框的标题
}
// 设置是否使用24小时制
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
// 设置日期时间设置完成后的回调监听器
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
// 更新对话框标题,显示当前选中的日期时间
private void updateTitle(long date) {
// 设置日期时间显示的格式
int flag =
DateUtils.FORMAT_SHOW_YEAR | // 显示年份
DateUtils.FORMAT_SHOW_DATE | // 显示日期
DateUtils.FORMAT_SHOW_TIME; // 显示时间
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; // 如果是24小时制显示24小时格式
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); // 格式化日期时间并设置为对话框标题
}
// 确认按钮点击事件的处理
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
// 调用回调接口,传递设置的日期时间
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,63 @@
/*
* 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 android.content.Context; // 引入Context类用于访问应用程序的上下文
import android.view.Menu; // 引入Menu类用于操作菜单
import android.view.MenuItem; // 引入MenuItem类用于菜单项的处理
import android.view.View; // 引入View类用于视图的操作
import android.view.View.OnClickListener; // 引入OnClickListener接口用于处理点击事件
import android.widget.Button; // 引入Button类用于显示按钮
import android.widget.PopupMenu; // 引入PopupMenu类用于显示弹出菜单
import android.widget.PopupMenu.OnMenuItemClickListener; // 引入PopupMenu的菜单项点击监听器
public class DropdownMenu {
private Button mButton; // 用于显示的按钮,点击后弹出菜单
private PopupMenu mPopupMenu; // 弹出菜单的实例
private Menu mMenu; // 菜单项的集合
// 构造函数初始化DropdownMenu设置按钮及其弹出菜单
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button; // 获取传入的按钮对象
mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮的背景图标为下拉菜单图标
mPopupMenu = new PopupMenu(context, mButton); // 初始化PopupMenu指定按钮作为弹出菜单的锚点
mMenu = mPopupMenu.getMenu(); // 获取PopupMenu中的Menu对象
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 使用传入的menuId加载菜单资源
mButton.setOnClickListener(new OnClickListener() { // 为按钮设置点击事件监听器
public void onClick(View v) {
mPopupMenu.show(); // 当按钮被点击时,显示弹出菜单
}
});
}
// 设置菜单项点击事件监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener); // 设置PopupMenu的菜单项点击监听器
}
}
// 根据菜单项的ID查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id); // 在菜单中查找具有指定ID的菜单项
}
// 设置按钮的文本
public void setTitle(CharSequence title) {
mButton.setText(title); // 设置按钮的文本为传入的title
}
}

@ -0,0 +1,89 @@
/*
* 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 android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
// FoldersListAdapter 是一个自定义的 CursorAdapter用于显示文件夹列表。
public class FoldersListAdapter extends CursorAdapter {
// 定义查询数据库时需要的列。
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
// 定义列索引常量。
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
// FoldersListAdapter 的构造函数。
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
// 新建视图的方法,返回一个 FolderListItem 实例。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
// 绑定视图与数据的方法。
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
// 根据位置获取文件夹名称的方法。
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
// FolderListItem 是一个内部类,表示列表中的单个文件夹项。
private class FolderListItem extends LinearLayout {
private TextView mName; // 用于显示文件夹名称的 TextView。
// FolderListItem 的构造函数。
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
// 绑定文件夹名称的方法。
public void bind(String name) {
mName.setText(name);
}
}
}

@ -0,0 +1,515 @@
/*
* 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 android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// NoteEditActivity 是一个用于编辑便签的 Activity。
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
// 内部类,用于持有头部视图的控件引用。
private class HeadViewHolder {
public TextView tvModified; // 修改日期的 TextView。
public ImageView ivAlertIcon; // 提醒图标的 ImageView。
public TextView tvAlertDate; // 提醒日期的 TextView。
public ImageView ibSetBgColor; // 设置背景颜色的 ImageView。
}
// 背景颜色选择按钮与颜色 ID 的映射。
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
// 背景颜色选择结果与按钮 ID 的映射。
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
// 字体大小选择按钮与大小 ID 的映射。
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
// 字体大小选择结果与按钮 ID 的映射。
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
// 日志标签。
private static final String TAG = "NoteEditActivity";
// 头部视图的 Holder。
private HeadViewHolder mNoteHeaderHolder;
// 头部视图面板、背景颜色选择器和字体大小选择器。
private View mHeadViewPanel;
private View mNoteBgColorSelector;
private View mFontSizeSelector;
// 编辑器和编辑器面板。
private EditText mNoteEditor;
private View mNoteEditorPanel;
// 正在编辑的便签。
private WorkingNote mWorkingNote;
// SharedPreferences 和字体大小 ID。
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
// SharedPreferences 中字体大小的键。
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
// 快捷方式图标标题的最大长度。
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
// 多选框标记。
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
// 编辑文本列表。
private LinearLayout mEditTextList;
// 用户查询字符串和正则表达式模式。
private String mUserQuery;
private Pattern mPattern;
// onCreate 方法,初始化 Activity。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
}
// onRestoreInstanceState 方法,恢复 Activity 状态。
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
// initActivityState 方法,初始化 Activity 状态。
private boolean initActivityState(Intent intent) {
// 省略部分代码...
}
// onResume 方法,恢复 Activity 时调用。
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}
// initNoteScreen 方法,初始化便签屏幕。
private void initNoteScreen() {
// 省略部分代码...
}
// onNewIntent 方法,处理新的 Intent。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
// onSaveInstanceState 方法,保存 Activity 状态。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 省略部分代码...
}
// dispatchTouchEvent 方法,分发触摸事件。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略部分代码...
}
// inRangeOfView 方法,判断触摸事件是否在视图范围内。
private boolean inRangeOfView(View view, MotionEvent ev) {
// 省略部分代码...
}
// initResources 方法,初始化资源。
private void initResources() {
// 省略部分代码...
}
// onPause 方法,暂停 Activity 时调用。
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
// updateWidget 方法,更新小部件。
private void updateWidget() {
// 省略部分代码...
}
}
/**
*
* @param v
*/
public void onClick(View v) {
int id = v.getId(); // 获取被点击视图的ID。
// 如果被点击的视图是设置背景颜色的按钮,则显示背景颜色选择器。
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(-View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) { // 如果被点击的是背景颜色按钮。
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); // 更改背景颜色ID。
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) { // 如果被点击的是字体大小按钮。
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id); // 更改字体大小ID。
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); // 保存新的字体大小偏好。
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
// 如果便签处于清单模式,更新清单视图;否则,更新文本外观。
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
}
/**
*
*/
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
/**
*
* @return truefalse
*/
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
/**
*
*/
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
/**
*
* @param menu
* @return truefalse
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
// 根据便签的文件夹ID决定加载哪个菜单。
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
// 根据便签的属性更新菜单项。
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}
/**
*
* @param item
* @return truefalse
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote();
break;
case R.id.menu_delete:
// 显示删除便签的确认对话框。
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop:
sendToDesktop();
break;
case R.id.menu_alert:
setReminder();
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
default:
break;
}
return true;
}
/**
* 便
*/
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date, true);
}
});
d.show();
}
/**
* 便{@link Intent#ACTION_SEND}{text/plain}
* @param context
* @param info 便
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}
/**
* 便
*/
private void createNewNote() {
// 在创建新便签之前保存当前正在编辑的便签。
saveNote();
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
/**
* 便
*/
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "错误的便签ID不应该发生");
}
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "删除便签错误");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "将便签移动到废纸篓文件夹错误,不应该发生");
}
}
}
mWorkingNote.markDeleted(true);
}
/**
*
* @return truefalse
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
*
* @param date
* @param set truefalse
*/
public void onClockAlertChanged(long date, boolean set) {
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
Log.e(TAG, "时钟提醒设置错误");
showToast(R.string.error_note_empty_for_clock);
}
}
/**
*
*/
public void onWidgetChanged() {
updateWidget();
}
/**
*
* @param index
* @param text
*/

@ -0,0 +1,304 @@
/*
* 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 android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
/*
* 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; // 定义类所在的包路径
// 引入相关的 Android 类
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R; // 引入资源文件中的R类
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText"; // 定义日志标签,用于调试输出
private int mIndex; // 用于表示当前编辑文本的索引
private int mSelectionStartBeforeDelete; // 用于记录删除前的文本选择开始位置
// 定义常见的URI Schemes
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
// 定义一个映射关系存储不同URI Scheme对应的资源ID
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话链接对应的资源ID
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // 网站链接对应的资源ID
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 邮件链接对应的资源ID
}
/**
* {@link NoteEditActivity}
*/
public interface OnTextViewChangeListener {
/**
*
*/
void onEditTextDelete(int index, String text);
/**
*
*/
void onEditTextEnter(int index, String text);
/**
*
*/
void onTextChange(int index, boolean hasText);
}
}
// 定义一个文本视图变化监听器的接口
private OnTextViewChangeListener mOnTextViewChangeListener;
// NoteEditText的构造函数当从AttributeSet创建时调用
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
// 设置当前文本编辑框的索引
public void setIndex(int index) {
mIndex = index;
}
// 设置文本视图变化监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// NoteEditText的构造函数当从AttributeSet和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// NoteEditText的构造函数当从AttributeSet、defStyle和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
// 处理触摸事件,用于实现自定义的文本选择行为
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event);
}
// 处理按键事件例如Enter和Delete键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
// 处理按键释放事件例如Enter和Delete键
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
// 处理焦点变化事件,用于在文本编辑框失去焦点时通知监听器
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
// 创建上下文菜单,用于实现长按文本时弹出的菜单
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}

@ -0,0 +1,156 @@
// 定义一个文本视图变化监听器的接口,用于监听文本编辑框中的变化事件
private OnTextViewChangeListener mOnTextViewChangeListener;
// NoteEditText的构造函数当从AttributeSet创建时调用
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
// 设置当前文本编辑框的索引,用于标识编辑框的位置
public void setIndex(int index) {
mIndex = index;
}
// 设置文本视图变化监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// NoteEditText的构造函数当从AttributeSet和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// NoteEditText的构造函数当从AttributeSet、defStyle和 defStyle创建时调用
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
// 处理触摸事件,用于实现自定义的文本选择行为
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算触摸点在文本中的位置,并设置光标
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event);
}
// 处理按键事件例如Enter和Delete键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 如果设置了监听器消费Enter键事件
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 在删除之前记录光标的位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
// 处理按键释放事件例如Enter和Delete键
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
// 如果设置了监听器,并且在删除之前光标位于文本开头,则通知监听器删除事件
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
// 如果设置了监听器通知监听器Enter键事件创建新的文本行
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
// 处理焦点变化事件,用于在文本编辑框失去焦点时通知监听器
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
// 如果文本编辑框失去焦点且文本为空,通知监听器文本变化
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
// 创建上下文菜单,用于实现长按文本时弹出的菜单
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 如果选中的文本包含URL添加一个菜单项用于打开链接
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 打开链接
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}

@ -0,0 +1,368 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
// NotesListActivity 是一个用于显示便签列表的 Activity。
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// 定义查询令牌,用于处理数据库查询。
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
// 定义菜单项的常量。
private static final int MENU_FOLDER_DELETE = 0;
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 定义 SharedPreferences 中保存的“添加介绍”的键。
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
// 枚举类,表示当前列表的编辑状态。
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
};
private ListEditState mState;
// 后台查询处理器。
private BackgroundQueryHandler mBackgroundQueryHandler;
// 便签列表适配器。
private NotesListAdapter mNotesListAdapter;
// 便签列表视图。
private ListView mNotesListView;
// 添加新便签的按钮。
private Button mAddNewNote;
// 处理触摸事件的变量。
private boolean mDispatch;
private int mOriginY;
private int mDispatchY;
// 标题栏。
private TextView mTitleBar;
// 当前文件夹ID。
private long mCurrentFolderId;
// 内容解析器。
private ContentResolver mContentResolver;
// 模式回调。
private ModeCallback mModeCallBack;
// 日志标签。
private static final String TAG = "NotesListActivity";
// 便签列表视图滚动速率。
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
// 焦点便签数据项。
private NoteItemData mFocusNoteDataItem;
// 普通选择和根文件夹选择的SQL语句。
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
// 请求代码常量。
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
// onCreate 方法,初始化 Activity。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list);
initResources();
/**
* 使
*/
setAppInfoFromRawRes();
}
// onActivityResult 方法,处理 Activity 结果。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
mNotesListAdapter.changeCursor(null);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
// 从 raw 资源文件中设置应用程序信息。
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
in = getResources().openRawResource(R.raw.introduction);
if (in != null) {
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
char [] buf = new char[1024];
int len = 0;
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
note.setWorkingText(sb.toString());
if (note.saveNote()) {
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
Log.e(TAG, "Save introduction note error");
return;
}
}
}
}
/**
* Activity
*/
@Override
protected void onStart() {
super.onStart(); // 调用父类的 onStart 方法。
startAsyncNotesListQuery(); // 开始异步查询便签列表。
}
/**
*
*/
private void initResources() {
mContentResolver = this.getContentResolver(); // 获取内容解析器。
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); // 创建后台查询处理器。
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹。
mNotesListView = (ListView) findViewById(R.id.notes_list); // 获取便签列表视图。
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); // 添加列表底部视图。
mNotesListView.setOnItemClickListener(new OnListItemClickListener()); // 设置列表项点击事件监听器。
mNotesListView.setOnItemLongClickListener(this); // 设置列表项长按事件监听器。
mNotesListAdapter = new NotesListAdapter(this); // 创建便签列表适配器。
mNotesListView.setAdapter(mNotesListAdapter); // 设置列表适配器。
mAddNewNote = (Button) findViewById(R.id.btn_new_note); // 获取添加新便签的按钮。
mAddNewNote.setOnClickListener(this); // 设置按钮点击事件监听器。
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 设置按钮触摸事件监听器。
mDispatch = false; // 初始化 dispatch 变量。
mDispatchY = 0; // 初始化 dispatchY 变量。
mOriginY = 0; // 初始化 originY 变量。
mTitleBar = (TextView) findViewById(R.id.tv_title_bar); // 获取标题栏。
mState = ListEditState.NOTE_LIST; // 初始化列表编辑状态。
mModeCallBack = new ModeCallback(); // 创建模式回调对象。
}
/**
* ModeCallback ListView
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu; // 下拉菜单。
private ActionMode mActionMode; // 动作模式。
private MenuItem mMoveMenu; // 移动菜单项。
/**
*
* @param mode
* @param menu
* @return
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 省略部分代码...
}
/**
*
*/
private void updateMenu() {
// 省略部分代码...
}
/**
*
* @param mode
* @param menu
* @return
*/
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
/**
*
* @param mode
* @param item
* @return
*/
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
/**
*
* @param mode
*/
public void onDestroyActionMode(ActionMode mode) {
// 省略部分代码...
}
/**
*
*/
public void finishActionMode() {
mActionMode.finish();
}
/**
*
* @param mode
* @param position
* @param id ID
* @param checked
*/
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
// 省略部分代码...
}
/**
*
* @param item
* @return
*/
public boolean onMenuItemClick(MenuItem item) {
// 省略部分代码...
}
}
/**
* NewNoteOnTouchListener 便
*/
private class NewNoteOnTouchListener implements OnTouchListener {
/**
*
* @param v
* @param event
* @return
*/
public boolean onTouch(View v, MotionEvent event) {
// 省略部分代码...
}
}
/**
* 便
*/
private void startAsyncNotesListQuery() {
// 省略部分代码...
}
/**
* BackgroundQueryHandler AsyncQueryHandler
*/
private final class BackgroundQueryHandler extends AsyncQueryHandler {
/**
*
* @param contentResolver
*/
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
/**
*
* @param token
* @param cookie
* @param cursor
*/
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// 省略部分代码...
}
}

@ -0,0 +1,196 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
// NotesListAdapter 是一个继承自 CursorAdapter 的类,用于为便签列表提供数据和视图。
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter"; // 日志标签。
private Context mContext; // 上下文对象。
private HashMap<Integer, Boolean> mSelectedIndex; // 存储选中项的哈希映射。
private int mNotesCount; // 便签数量。
private boolean mChoiceMode; // 是否处于选择模式。
// AppWidgetAttribute 内部类,用于存储小部件的属性。
public static class AppWidgetAttribute {
public int widgetId; // 小部件ID。
public int widgetType; // 小部件类型。
};
// NotesListAdapter 的构造函数。
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
// 新建视图的方法。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
// 绑定视图和数据的方法。
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
// 设置项的选中状态。
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
// 获取是否处于选择模式。
public boolean isInChoiceMode() {
return mChoiceMode;
}
// 设置选择模式。
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
// 全选或全不选。
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
}
// 获取选中项的ID集合。
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
// 获取选中的小部件属性集合。
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
// 获取选中项的数量。
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
// 判断是否全部选中。
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
// 判断项是否被选中。
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
// 当内容改变时调用。
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
// 当游标改变时调用。
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
// 计算便签数量。
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -0,0 +1,127 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
// NotesListItem 类继承自LinearLayout用于显示便签列表中的单个项目。
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 用于显示提醒图标的ImageView。
private TextView mTitle; // 显示便签标题的TextView。
private TextView mTime; // 显示便签修改时间的TextView。
private TextView mCallName; // 显示通话记录姓名的TextView。
private NoteItemData mItemData; // 绑定到此列表项的便签数据。
private CheckBox mCheckBox; // 用于选择模式的CheckBox。
// NotesListItem的构造函数初始化视图和组件。
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
// 绑定便签数据到列表项。
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
// 根据便签数据的类型和属性设置视图的显示。
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setBackground(data);
}
// 设置背景色的方法。
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
// 根据便签的类型和位置设置不同的背景。
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
// 获取绑定到此列表项的便签数据。
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,266 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License, Version 2.0
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
// NotesPreferenceActivity 是一个用于设置便签应用偏好的 Activity。
public class NotesPreferenceActivity extends PreferenceActivity {
// 偏好设置文件的名称。
public static final String PREFERENCE_NAME = "notes_preferences";
// 同步账户名称的键。
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
// 最后同步时间的键。
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
// 设置背景颜色的键。
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
// 同步账户的键。
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
// 权限过滤键。
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory; // 账户类别的 PreferenceCategory。
private GTaskReceiver mReceiver; // 用于接收 GTask 同步状态的 BroadcastReceiver。
private Account[] mOriAccounts; // 原始账户数组。
private boolean mHasAddedAccount; // 是否添加了账户。
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 使用应用图标作为导航。
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从 XML 文件中添加偏好设置。
addPreferencesFromResource(R.xml.preferences);
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
mOriAccounts = null;
// 从布局文件中添加设置头部。
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
@Override
protected void onResume() {
super.onResume();
// 自动设置同步账户,如果用户添加了新账户。
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
}
refreshUI();
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
// 加载账户偏好设置。
private void loadAccountPreference() {
mAccountCategory.removeAll();
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// 第一次设置账户。
showSelectAccountAlertDialog();
} else {
// 如果已经设置了账户,需要提示用户风险。
showChangeAccountConfirmAlertDialog();
}
} else {
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
mAccountCategory.addPreference(accountPref);
}
// 加载同步按钮。
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 设置按钮状态。
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 设置最后同步时间。
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
// 刷新 UI。
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
// 显示选择账户的对话框。
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
items[index++] = account.name;
}
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI();
}
});
}
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
startActivityForResult(intent, -1);
dialog.dismiss();
}
});
}
// 显示更改账户确认对话框。
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string

@ -0,0 +1,144 @@
/*
* 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.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import android.widget.RemoteViews;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
// 继承自AppWidgetProvider的抽象类用于管理便签小部件。
public abstract class NoteWidgetProvider extends AppWidgetProvider {
// 定义查询数据库时需要的列。
public static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};
// 定义列索引常量。
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
// 日志标签用于Logcat中识别日志来源。
private static final String TAG = "NoteWidgetProvider";
// 当小部件被删除时调用更新数据库中对应的笔记widget id为无效值。
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
}
}
// 获取小部件相关的笔记信息。
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
// 更新小部件显示。
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
// 更新小部件显示,考虑隐私模式。
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
String snippet = "";
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
if (c != null) {
c.close();
}
// 设置小部件的布局和行为。
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* 宿PendingIntent
*/
PendingIntent pendingIntent = null;
if (privacyMode) {
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
// 获取背景资源ID的方法需由子类实现。
protected abstract int getBgResourceId(int bgId);
// 获取布局ID的方法需由子类实现。
protected abstract int getLayoutId();
// 获取小部件类型的方法,需由子类实现。
protected abstract int getWidgetType();
}

@ -0,0 +1,55 @@
/*
* 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 的具体实现类用于提供2x2网格大小的便签小部件。
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 当小部件需要更新时调用此方法。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的 update 方法来执行更新。
super.update(context, appWidgetManager, appWidgetIds);
}
// 返回小部件的布局资源ID。
@Override
protected int getLayoutId() {
// 返回2x2网格小部件的布局资源ID。
return R.layout.widget_2x;
}
// 根据传入的背景ID返回对应的资源ID。
@Override
protected int getBgResourceId(int bgId) {
// 使用 ResourceParser 类的静态方法来获取2x2网格小部件的背景资源ID。
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
// 返回小部件的类型。
@Override
protected int getWidgetType() {
// 返回小部件的类型为TYPE_WIDGET_2X这是一个定义在Notes类中的常量。
return Notes.TYPE_WIDGET_2X;
}
}

@ -0,0 +1,55 @@
/*
* 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_4x 类继承自 NoteWidgetProvider 类用于提供4x4网格大小的便签小部件。
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
// 当小部件需要更新时,系统会调用此方法。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的 update 方法来更新小部件。
super.update(context, appWidgetManager, appWidgetIds);
}
// 返回小部件的布局资源ID。
@Override
protected int getLayoutId() {
// 返回4x4网格小部件的布局资源ID。
return R.layout.widget_4x;
}
// 根据传入的背景ID返回对应的资源ID。
@Override
protected int getBgResourceId(int bgId) {
// 使用 ResourceParser 类的静态方法来获取4x4网格小部件的背景资源ID。
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
// 返回小部件的类型。
@Override
protected int getWidgetType() {
// 返回小部件的类型为TYPE_WIDGET_4X这是一个定义在Notes类中的常量。
return Notes.TYPE_WIDGET_4X;
}
}
Loading…
Cancel
Save