修复合并bug,上传res文件夹

pull/30/head
white-yj8109 4 weeks ago
parent c0671cccd9
commit cfb47d5958

@ -66,6 +66,7 @@ public class Notes {
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
public static final String ENCRYPTED_FOLDER = "vnd.android.cursor.item/encrypted_folder";
}
/**
@ -185,6 +186,17 @@ public class Notes {
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
/**
* Flag to indicate this note is a habit note
* <P> Type : INTEGER (0/1) </P>
*/
public static final String IS_HABIT = "is_habit";
/**
* Habit configuration stored as JSON string
* <P> Type : TEXT </P>
*/
public static final String HABIT_CONFIG = "habit_config";
}
//Data表数据库列名常量接口

@ -38,7 +38,7 @@ import net.micode.notes.data.NotesDatabaseHelper.TABLE;
*
* @Package: net.micode.notes.data
* @ClassName: NotesProvider
* @Description: 便
* @Description: java
*/
public class NotesProvider extends ContentProvider {
//uri匹配器

@ -25,11 +25,6 @@ import org.json.JSONException;
import org.json.JSONObject;
/**
* @Package: net.micode.notes.gtask.data
* @ClassName: MetaData
* @Description: Google Task便Google Task
*/
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();

@ -33,11 +33,7 @@ import java.util.ArrayList;
*
* @Package: net.micode.notes.gtask.data
* @ClassName: TaskList
* @Description: Node
* 1. Task
* 2. Google Task APIJSON
* 3.
* 4.
* @Description: java
*/
public class TaskList extends Node {
private static final String TAG = TaskList.class.getSimpleName();

@ -27,7 +27,32 @@ import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* GTaskASyncTask - Google Task
*
*
* 1. 线Google Task
* 2.
* 3.
* 4.
*
*
* - AsyncTask线
* -
* -
*
*
* 1. doInBackground()
* 2. onProgressUpdate()
* 3. onPostExecute()
* 4. cancelSync()
*
* 线
* 使AsyncTaskUI线线
*
* @author MiCode Open Source Community
* @version 1.0
*/
/**
*
* @Package: net.micode.notes.gtask.remote
@ -102,7 +127,16 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
/**
*
*
*
* 1.
* 2. GTaskManager
*
* @param unused 使Void
* @return
*/
/**
* @method doInBackground
* @description

@ -912,7 +912,7 @@ public class GTaskManager {
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
mCancelled = true;
}

@ -60,6 +60,10 @@ public class WorkingNote {
private boolean mIsDeleted;
// habit fields
private boolean mIsHabit;
private String mHabitConfig;
private NoteSettingChangedListener mNoteSettingStatusListener;// 设置变化监听器
//数据表查询字段投影 用于查询便签数据
@ -81,7 +85,10 @@ public class WorkingNote {
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
,
NoteColumns.IS_HABIT,
NoteColumns.HABIT_CONFIG
};
private static final int DATA_ID_COLUMN = 0;
@ -102,6 +109,8 @@ public class WorkingNote {
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
private static final int NOTE_HABIT_FLAG_COLUMN = 6;
private static final int NOTE_HABIT_CONFIG_COLUMN = 7;
// New note construct 创建新便签
private WorkingNote(Context context, long folderId) {
@ -141,6 +150,17 @@ public class WorkingNote {
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
// habit fields (may not exist on older DB versions)
try {
mIsHabit = cursor.getInt(NOTE_HABIT_FLAG_COLUMN) == 1;
} catch (Exception e) {
mIsHabit = false;
}
try {
mHabitConfig = cursor.getString(NOTE_HABIT_CONFIG_COLUMN);
} catch (Exception e) {
mHabitConfig = "";
}
}
cursor.close();
} else {
@ -285,6 +305,24 @@ public class WorkingNote {
}
}
// 设置/取消习惯便签并保存配置信息config 为 JSON 字符串,可为空)
public void setHabit(boolean isHabit, String config) {
if (mIsHabit != isHabit || (config != null && !config.equals(mHabitConfig))) {
mIsHabit = isHabit;
mHabitConfig = config == null ? "" : config;
mNote.setNoteValue(NoteColumns.IS_HABIT, String.valueOf(isHabit ? 1 : 0));
mNote.setNoteValue(NoteColumns.HABIT_CONFIG, mHabitConfig);
}
}
public boolean isHabit() {
return mIsHabit;
}
public String getHabitConfig() {
return mHabitConfig == null ? "" : mHabitConfig;
}
//设置清单模式
public void setCheckListMode(int mode) {
if (mMode != mode) {

@ -41,34 +41,38 @@ public class BackupUtils {
// Singleton stuff
private static BackupUtils sInstance;
//ynchronized 关键字,代表这个方法加锁,防止多进程混乱
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
//如果当前备份不存在,则新声明一个
sInstance = new BackupUtils(context);
}
return sInstance;
}
// 当前 SD 卡未挂载
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 文件不存在
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 数据格式损坏(可能被其他程序修改)
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// 运行时异常导致备份/恢复失败
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// 备份或重新储存成功???
// Backup or restore success
public static final int STATE_SUCCESS = 4;
//文本提取
private TextExport mTextExport;
//初始化类将该类设置为context接口格式提供了访问资源、启动组件、获取系统服务、管理文件、执行权限检查等。
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
// 获取外部存储的根目录便于进行api操作
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// mTextExport是一个静态内部类,提供了导出功能
public int exportToText() {
return mTextExport.exportToText();
}
@ -82,20 +86,20 @@ public class BackupUtils {
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = { //文本基础信息
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET, //文本片段
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
//定义类级别的常量.p s f下面的 数字代表在字符串中存储位置
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = { //数据基础信息,这个数据是啥数据???
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
@ -106,7 +110,7 @@ public class BackupUtils {
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1; //导出笔记时,先查询这条笔记的 DATA_COLUMN_MIME_TYPE 值,判断格式
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
@ -122,7 +126,7 @@ public class BackupUtils {
private String mFileDirectory;
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);//从array.xml里获取字符串数组信息
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
@ -133,33 +137,33 @@ public class BackupUtils {
}
/**
* Export the folder identified by folder id to text 便
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId //引用的别的类的啥父母ID
folderId
}, null);
if (notesCursor != null) { //遍历cursor的数据
if (notesCursor.moveToFirst()) { //在开头
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm), //R.string.xxx 是一个引用字符串资源的标识符,它直接指向资源文件中的字符串,用gststring执行
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); //getXXXX是结构
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext()); //没有下一个就终止,代表数据遍历完毕
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream便
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
@ -171,14 +175,14 @@ public class BackupUtils {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) { //有电话的note
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
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),
phoneNumber));
}
// Print call date
@ -212,10 +216,9 @@ public class BackupUtils {
}
/**
* Note will be exported as text which is user readable ,
* Note will be exported as text which is user readable
*/
public int exportToText() {
//判断两个失败条件
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
@ -226,22 +229,22 @@ public class BackupUtils {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes 输出在文件夹中的便签
// First export folder and its notes
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); //xinxi
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { //这个判断何意味为啥一个cursor一个context
folderName = mContext.getString(R.string.call_record_folder_name); //Context.getString 是 Android 中用于从资源文件中获取字符串的方法
} else { //cursor.getString 是 Android 中用于从数据库中获取字符串类型数据的方法
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)) {
@ -254,7 +257,7 @@ public class BackupUtils {
folderCursor.close();
}
// Export notes in root's folder,输出根目录中的便签
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
@ -276,16 +279,15 @@ public class BackupUtils {
}
ps.close();
return STATE_SUCCESS; //都能对应上
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format); //建一个指向 SD 卡指定目录的 TXT 导出文件
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
@ -293,9 +295,9 @@ public class BackupUtils {
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try { //try catch语法
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos); //返回操作该文件的 PrintStream 输出流
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
@ -308,25 +310,25 @@ public class BackupUtils {
}
/**
* Generate the text file to store imported data TXT
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString()); //stringbuilder转成srting再写进file
File file = new File(sb.toString());
try {
if (!filedir.exists()) {
filedir.mkdir(); //文件目录
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile(); //文件
file.createNewFile();
}
return file;
} catch (SecurityException e) {

@ -37,7 +37,6 @@ import java.util.HashSet;
public class DataUtils {
public static final String TAG = "DataUtils";
//批量操作删除便签发生错误就返回flase删除成功就返回true
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
@ -47,42 +46,41 @@ public class DataUtils {
Log.d(TAG, "no id is in the hashset");
return true;
}
// 构建删除操作拼接标签ID到标签ContentUR
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));
//创建一个 ContentProviderOperation.Builder删除指定id的标签 只查询 ID 为 id 的那一条记录
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {////上面的一条执行数据库操作删除并返回结果数组
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) { //记录错误信息
} 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);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
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) {
@ -93,9 +91,10 @@ public class DataUtils {
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); //创建更新,
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
builder.withValue(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
operationList.add(builder.build());
}
@ -113,21 +112,11 @@ public class DataUtils {
}
return false;
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) {
//查询设备中「类型为文件夹TYPE_FOLDER」且「父文件夹 ID 不等于回收站 IDID_TRASH_FOLER」的文件夹总数。
'''
Cursor query(
Uri uri, // 要查询的数据 URI
String[] projection, // 要返回的列字段null 表示所有列
String selection, // WHERE 子句(不含 "WHERE" 关键字)
String[] selectionArgs, // WHERE 子句中占位符 ? 的实际值
String sortOrder // 排序方式(如 "date DESC"
);
'''
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
@ -138,7 +127,7 @@ Cursor query(
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
count = cursor.getInt(0);//从当前游标指向的行中,读取第 0 列(第一列)的整数类型数据
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
@ -148,11 +137,11 @@ Cursor query(
}
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, //笔记的 parent_id 不能等于回收站的 ID即不在回收站中
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
@ -165,7 +154,7 @@ Cursor query(
}
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);
@ -179,7 +168,7 @@ Cursor query(
}
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);
@ -193,12 +182,12 @@ Cursor query(
}
return exist;
}
//查看是否存在一个名字是XX的文件夹
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 + "=?", //存储在snippet的名字和下面的name一样
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) {
@ -209,9 +198,9 @@ Cursor query(
}
return exist;
}
//获得文件夹下的便签,并仅返回它们的 Widget ID 和 Widget 类型
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, //获得便签
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
@ -236,7 +225,7 @@ Cursor query(
}
return set;
}
//获得电话号
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
@ -246,7 +235,7 @@ Cursor query(
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0); //返回电话号
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
@ -255,7 +244,7 @@ Cursor query(
}
return "";
}
//通过电话号和日期查找便签
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
@ -276,7 +265,7 @@ Cursor query(
}
return 0;
}
//通过id获得名字
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
@ -294,15 +283,112 @@ Cursor query(
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
//从一段文本snippet中提取作为标题
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
snippet = snippet.trim();//清空无用制表符
int index = snippet.indexOf('\n');//index即录字符串中第一个换行符 \n 出现的位置
snippet = snippet.trim();
int index = snippet.indexOf('\n');
if (index != -1) {
snippet = snippet.substring(0, index);//只获取换行前的部分(核心)。
snippet = snippet.substring(0, index);
}
}
return snippet;
}
/**
* 便ID
* @param resolver ContentResolver
* @param folderId ID
* @return 便IDHashSet
*/
public static HashSet<Long> getNotesInFolder(ContentResolver resolver, long folderId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.ID },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
null);
HashSet<Long> ids = new HashSet<Long>();
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
try {
ids.add(cursor.getLong(0));
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get note id fails " + e.toString());
}
} while (cursor.moveToNext());
}
cursor.close();
}
return ids;
}
public static boolean batchMoveToTrash(ContentResolver resolver, HashSet<Long> ids, long originFolderId) {
if (ids == null || ids.isEmpty()) {
Log.d(TAG, "the ids is null or empty");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
long now = System.currentTimeMillis();
// 1. 更新便签的PARENT_ID为回收站ID
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
builder.withValue(NoteColumns.ORIGIN_PARENT_ID, originFolderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
builder.withValue(NoteColumns.MODIFIED_DATE, now);
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0) {
Log.d(TAG, "move to trash 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 moveNotesToTrashForFolder(ContentResolver resolver, long folderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.ORIGIN_PARENT_ID, folderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
resolver.update(Notes.CONTENT_NOTE_URI, values,
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) });
}
/**
*
* @param resolver ContentResolver
* @param folderId ID
* @return
*/
public static boolean isEncryptedFolder(ContentResolver resolver, long folderId) {
// 查询是否存在该文件夹的加密数据
String selection = Notes.DataColumns.NOTE_ID + "=? AND " + Notes.DataColumns.MIME_TYPE + "=?";
String[] selectionArgs = new String[] {
String.valueOf(folderId),
Notes.DataConstants.ENCRYPTED_FOLDER
};
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, null, selection, selectionArgs, null);
boolean isEncrypted = false;
if (cursor != null) {
isEncrypted = cursor.getCount() > 0;
cursor.close();
}
return isEncrypted;
}
}

@ -0,0 +1,238 @@
/*
* 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.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import net.micode.notes.R;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
public class DoodleDialog extends Dialog {
public interface OnDoodleSavedListener {
void onSaved(String localPath);
}
private final Context mContext;
private final OnDoodleSavedListener mListener;
private DoodleView mDoodleView;
public DoodleDialog(Context context, OnDoodleSavedListener listener) {
super(context);
mContext = context;
mListener = listener;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.doodle_title);
setContentView(createContentView());
WindowManager.LayoutParams params = getWindow().getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
getWindow().setAttributes(params);
}
private View createContentView() {
LinearLayout root = new LinearLayout(mContext);
root.setOrientation(LinearLayout.VERTICAL);
int padding = dpToPx(12);
root.setPadding(padding, padding, padding, padding);
mDoodleView = new DoodleView(mContext);
LinearLayout.LayoutParams canvasParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 0);
canvasParams.weight = 1f;
root.addView(mDoodleView, canvasParams);
LinearLayout actions = new LinearLayout(mContext);
actions.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams actionParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
actions.setLayoutParams(actionParams);
Button clearButton = new Button(mContext);
clearButton.setText(R.string.doodle_clear);
clearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDoodleView.clear();
}
});
Button cancelButton = new Button(mContext);
cancelButton.setText(android.R.string.cancel);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
Button saveButton = new Button(mContext);
saveButton.setText(R.string.doodle_save);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String path = saveDoodle();
if (!TextUtils.isEmpty(path)) {
if (mListener != null) {
mListener.onSaved(path);
}
dismiss();
} else {
Toast.makeText(mContext, R.string.doodle_save_failed, Toast.LENGTH_SHORT).show();
}
}
});
actions.addView(clearButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
actions.addView(cancelButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
actions.addView(saveButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
root.addView(actions);
return root;
}
private String saveDoodle() {
Bitmap bitmap = mDoodleView.exportBitmap();
if (bitmap == null) {
return null;
}
try {
File baseDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (baseDir == null) {
baseDir = mContext.getFilesDir();
}
File appDir = new File(baseDir, "note_images");
if (!appDir.exists() && !appDir.mkdirs()) {
return null;
}
File file = new File(appDir, "doodle_" + System.currentTimeMillis() + ".png");
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
return file.getAbsolutePath();
} catch (Exception e) {
return null;
}
}
private int dpToPx(int dp) {
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
return Math.round(dm.density * dp);
}
private static class DoodleView extends View {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final List<Path> mPaths = new ArrayList<>();
private Path mCurrentPath;
DoodleView(Context context) {
super(context);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(6f);
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Path path : mPaths) {
canvas.drawPath(path, mPaint);
}
if (mCurrentPath != null) {
canvas.drawPath(mCurrentPath, mPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mCurrentPath = new Path();
mCurrentPath.moveTo(x, y);
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
if (mCurrentPath != null) {
mCurrentPath.lineTo(x, y);
invalidate();
}
return true;
case MotionEvent.ACTION_UP:
if (mCurrentPath != null) {
mCurrentPath.lineTo(x, y);
mPaths.add(mCurrentPath);
mCurrentPath = null;
invalidate();
}
return true;
default:
return false;
}
}
void clear() {
mPaths.clear();
mCurrentPath = null;
invalidate();
}
Bitmap exportBitmap() {
if (getWidth() <= 0 || getHeight() <= 0) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.WHITE);
for (Path path : mPaths) {
canvas.drawPath(path, mPaint);
}
return bitmap;
}
}
}

@ -0,0 +1,218 @@
/*
* 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.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.net.Uri;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
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.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class EncryptedFolderManager {
private static final String TAG = "EncryptedFolderManager";
private final Context mContext;
private final ContentResolver mResolver;
private final Callback mCallback;
public interface Callback {
void onEncryptedFolderCreated();
void onEncryptedFolderUnlocked(NoteItemData data);
}
public EncryptedFolderManager(Context context, ContentResolver resolver, Callback callback) {
mContext = context;
mResolver = resolver;
mCallback = callback;
}
public void showCreateEncryptedFolderDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_encrypted_folder, null);
final EditText etName = (EditText) view.findViewById(R.id.et_encrypted_folder_name);
final EditText etQuestion = (EditText) view.findViewById(R.id.et_encrypted_question);
final EditText etAnswer = (EditText) view.findViewById(R.id.et_encrypted_answer);
etAnswer.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
builder.setTitle(R.string.encrypted_folder_title);
builder.setView(view);
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
final Dialog dialog = builder.show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = etName.getText().toString().trim();
String question = etQuestion.getText().toString().trim();
String answer = etAnswer.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
etName.setError(mContext.getString(R.string.hint_foler_name));
return;
}
if (TextUtils.isEmpty(question)) {
etQuestion.setError(mContext.getString(R.string.encrypted_question_empty));
return;
}
if (TextUtils.isEmpty(answer)) {
etAnswer.setError(mContext.getString(R.string.encrypted_answer_empty));
return;
}
if (DataUtils.checkVisibleFolderName(mResolver, name)) {
Toast.makeText(mContext,
mContext.getString(R.string.folder_exist, name), Toast.LENGTH_LONG).show();
return;
}
long folderId = createEncryptedFolder(name, question, answer);
if (folderId > 0) {
dialog.dismiss();
if (mCallback != null) {
mCallback.onEncryptedFolderCreated();
}
}
}
});
}
public EncryptedFolderInfo getEncryptedFolderInfo(long folderId) {
Cursor cursor = mResolver.query(Notes.CONTENT_DATA_URI,
new String[] { DataColumns.DATA3, DataColumns.DATA4 },
DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?",
new String[] { String.valueOf(folderId), Notes.DataConstants.ENCRYPTED_FOLDER },
null);
if (cursor == null) {
return null;
}
try {
if (cursor.moveToFirst()) {
String question = cursor.getString(0);
String answerHash = cursor.getString(1);
if (!TextUtils.isEmpty(question) && !TextUtils.isEmpty(answerHash)) {
return new EncryptedFolderInfo(folderId, question, answerHash);
}
}
} finally {
cursor.close();
}
return null;
}
public void showEncryptedUnlockDialog(final EncryptedFolderInfo info, final NoteItemData data) {
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_encrypted_unlock, null);
TextView tvQuestion = (TextView) view.findViewById(R.id.tv_encrypted_question);
final EditText etAnswer = (EditText) view.findViewById(R.id.et_encrypted_answer);
tvQuestion.setText(info.question);
builder.setTitle(R.string.encrypted_unlock_title);
builder.setView(view);
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
final Dialog dialog = builder.show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String answer = etAnswer.getText().toString().trim();
if (TextUtils.isEmpty(answer)) {
etAnswer.setError(mContext.getString(R.string.encrypted_answer_empty));
return;
}
if (!TextUtils.equals(hashAnswer(answer), info.answerHash)) {
Toast.makeText(mContext, R.string.encrypted_answer_wrong,
Toast.LENGTH_SHORT).show();
return;
}
dialog.dismiss();
if (mCallback != null) {
mCallback.onEncryptedFolderUnlocked(data);
}
}
});
}
private long createEncryptedFolder(String name, String question, String answer) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = mResolver.insert(Notes.CONTENT_NOTE_URI, values);
if (uri == null) {
return -1;
}
long folderId = -1;
try {
folderId = Long.parseLong(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Create encrypted folder failed", e);
return -1;
}
ContentValues dataValues = new ContentValues();
dataValues.put(DataColumns.NOTE_ID, folderId);
dataValues.put(DataColumns.MIME_TYPE, Notes.DataConstants.ENCRYPTED_FOLDER);
dataValues.put(DataColumns.DATA3, question);
dataValues.put(DataColumns.DATA4, hashAnswer(answer));
mResolver.insert(Notes.CONTENT_DATA_URI, dataValues);
return folderId;
}
private String hashAnswer(String answer) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] result = digest.digest(answer.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : result) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "Hash error", e);
return "";
}
}
public static class EncryptedFolderInfo {
private final long folderId;
private final String question;
private final String answerHash;
private EncryptedFolderInfo(long folderId, String question, String answerHash) {
this.folderId = folderId;
this.question = question;
this.answerHash = answerHash;
}
}
}

@ -15,7 +15,7 @@
*/
package net.micode.notes.tool;
//定义一些与 Google TasksGTaskJSON 数据交互相关的常量字符串,为与 Google Tasks 同步相关的 JSON 数据结构提供标准化的字段名常量便于在GTask上同步
public class GTaskStringUtils {
public final static String GTASK_JSON_ACTION_ID = "action_id";

@ -0,0 +1,172 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import net.micode.notes.R;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import jp.wasabeef.richeditor.RichEditor;
public class ImageInsertHelper {
private static final String TAG = "ImageInsertHelper";
private final Activity mActivity;
private final int mRequestCode;
public static class Result {
public final boolean success;
public final String localPath;
public final String html;
private Result(boolean success, String localPath, String html) {
this.success = success;
this.localPath = localPath;
this.html = html;
}
}
public ImageInsertHelper(Activity activity, int requestCode) {
mActivity = activity;
mRequestCode = requestCode;
}
public void startPickImage() {
try {
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
} else {
intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
}
intent.setType("image/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
}
mActivity.startActivityForResult(intent, mRequestCode);
} catch (ActivityNotFoundException e) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
mActivity.startActivityForResult(intent, mRequestCode);
} catch (ActivityNotFoundException ex) {
Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
Log.e(TAG, "No image picker available", ex);
}
}
}
public Result handleActivityResult(int requestCode, int resultCode, Intent data, RichEditor editor) {
if (requestCode != mRequestCode) {
return null;
}
if (resultCode != Activity.RESULT_OK || data == null) {
return new Result(false, null, null);
}
Uri uri = data.getData();
if (uri == null) {
Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
return new Result(false, null, null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
try {
mActivity.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.w(TAG, "Persistable uri permission not granted", e);
}
}
String localImagePath = saveImageToLocal(uri);
if (TextUtils.isEmpty(localImagePath)) {
return new Result(false, null, null);
}
String newHtml = appendImageHtml(editor, localImagePath);
return new Result(true, localImagePath, newHtml);
}
private String appendImageHtml(RichEditor editor, String localImagePath) {
String imgHtmlTag = buildImageHtmlTag(localImagePath);
String curHtml = normalizeEditorHtml(editor.getHtml());
String newHtml = curHtml + imgHtmlTag;
editor.setHtml(newHtml);
editor.focusEditor();
return newHtml;
}
String buildImageHtmlTag(String localImagePath) {
String imgUrl = Uri.fromFile(new File(localImagePath)).toString();
return "<img src=\"" + imgUrl + "\" width=\"200\" height=\"200\"/><br/>";
}
private String normalizeEditorHtml(String html) {
if (TextUtils.isEmpty(html) || "null".equalsIgnoreCase(html)) {
return "";
}
return html;
}
private String saveImageToLocal(Uri uri) {
try {
File baseDir = mActivity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (baseDir == null) {
baseDir = mActivity.getFilesDir();
}
File appDir = new File(baseDir, "note_images");
if (!appDir.exists() && !appDir.mkdirs()) {
Log.e(TAG, "Create image directory failed: " + appDir.getAbsolutePath());
Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
return null;
}
String fileName = "note_" + System.currentTimeMillis() + ".jpg";
File targetFile = new File(appDir, fileName);
try (InputStream is = mActivity.getContentResolver().openInputStream(uri);
OutputStream os = new FileOutputStream(targetFile)) {
if (is == null) {
Log.e(TAG, "Open image stream failed: " + uri);
Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
return null;
}
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
}
return targetFile.getAbsolutePath();
} catch (Exception e) {
Log.e(TAG, "Save image failed", e);
Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
return null;
}
}
}

@ -19,7 +19,7 @@ package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R; //资源文件夹中的文件被编译成R类的一部分R类在编译过程中会生成
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
@ -40,7 +40,6 @@ public class ResourceParser {
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
public static class NoteBgResources {
//引用背景颜色资源
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
@ -48,7 +47,7 @@ public class ResourceParser {
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,
@ -56,26 +55,32 @@ public class ResourceParser {
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
public static int getNoteBgResource(int id) {
if (id < 0 || id >= BG_EDIT_RESOURCES.length) {
return BG_EDIT_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_EDIT_RESOURCES[id];
}
public static int getNoteTitleBgResource(int id) {
if (id < 0 || id >= BG_EDIT_TITLE_RESOURCES.length) {
return BG_EDIT_TITLE_RESOURCES[0]; // 默认返回第一个标题背景资源
}
return BG_EDIT_TITLE_RESOURCES[id];
}
}
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( //获得一个实例,指向该偏好框架在给定上下文中使用的默认文件
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { //如果没指定就随机给个颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
return BG_DEFAULT_COLOR;
}
}
public static class NoteItemBgResources { //便签主题资源
public static class NoteItemBgResources {
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
@ -109,18 +114,30 @@ public class ResourceParser {
};
public static int getNoteBgFirstRes(int id) {
if (id < 0 || id >= BG_FIRST_RESOURCES.length) {
return BG_FIRST_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_FIRST_RESOURCES[id];
}
public static int getNoteBgLastRes(int id) {
if (id < 0 || id >= BG_LAST_RESOURCES.length) {
return BG_LAST_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_LAST_RESOURCES[id];
}
public static int getNoteBgSingleRes(int id) {
if (id < 0 || id >= BG_SINGLE_RESOURCES.length) {
return BG_SINGLE_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_SINGLE_RESOURCES[id];
}
public static int getNoteBgNormalRes(int id) {
if (id < 0 || id >= BG_NORMAL_RESOURCES.length) {
return BG_NORMAL_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_NORMAL_RESOURCES[id];
}
@ -139,6 +156,9 @@ public class ResourceParser {
};
public static int getWidget2xBgResource(int id) {
if (id < 0 || id >= BG_2X_RESOURCES.length) {
return BG_2X_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_2X_RESOURCES[id];
}
@ -151,6 +171,9 @@ public class ResourceParser {
};
public static int getWidget4xBgResource(int id) {
if (id < 0 || id >= BG_4X_RESOURCES.length) {
return BG_4X_RESOURCES[0]; // 默认返回第一个背景资源
}
return BG_4X_RESOURCES[id];
}
}

@ -32,7 +32,6 @@ public class TranslateUtils {
public static boolean isOnline(Context ctx) {
ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = cm.getActiveNetwork();
if (network == null) return false;
@ -46,6 +45,7 @@ public class TranslateUtils {
NetworkInfo ni = cm.getActiveNetworkInfo();
return ni != null && ni.isConnected();
}
}
public static String translateParagraph(String text, String targetLang) {

@ -55,7 +55,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.util.Calendar;
//继承Activity //承诺实现clickdismiss接口不继承功能
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private static final String TAG = "AlarmAlertActivity";
private long mNoteId;
@ -63,42 +62,35 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
private static final int SNIPPET_PREW_MAX_LEN = 60;
MediaPlayer mPlayer;
@Override //创建闹钟行为
@Override
protected void onCreate(Bundle savedInstanceState) {
//调用其父类Activity的onCreate方法来实现对界面的图画绘制工作。
super.onCreate(savedInstanceState);
//onSaveInstanceState()来保存当前activity的状态信息
requestWindowFeature(Window.FEATURE_NO_TITLE);
//final类似stactic获得窗口对象
final Window win = getWindow();
//为窗口添加各种标志flags从而改变窗口的行为和外观
//屏幕锁定时显示窗口,便于锁屏时启动闹钟
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
//LayoutParams 布局参数,规定子视图如何放置
if (!isScreenOn()) {//不锁平时
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON//当这个window对用户是可见状态,则保持设备屏幕不关闭且不变暗
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON//当window被添加或者显示,系统会点亮屏幕,
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON//允许在屏幕开启的时候锁定屏幕
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);//插入window的矩形大小,来确保内容不会被装饰物(如状态栏)掩盖
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();
try { //传递到当前组件的 URI 数据截取Uri内的字符串的第二个“/”右边的字符及id
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
//根据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();// 将异常的详细信息输出到标准错误流
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer();
//如果不在回收站就显示文字并发出声音
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
int isHabit = intent.getIntExtra("habit_alarm", 0);
if (isHabit == 1) {
@ -112,27 +104,26 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
}
private boolean isScreenOn() { //由电量判断屏幕状态
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
private void playAlarmSound() {
//由铃声管理器获得默认铃声的uri
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.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);//设置循环播放
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
@ -150,11 +141,10 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
private void showActionDialog() {
//AlertDialog.Builder是对话框组件用于显示信息
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this); //setPositive/Negative/NeutralButton()设置:确定,取消,中立按钮;
dialog.setPositiveButton(R.string.notealert_ok, this);
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
@ -344,20 +334,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);//执行安卓内置操作查看数据
intent.putExtra(Intent.EXTRA_UID, mNoteId);//向目标页面传递笔记的ID
startActivity(intent);//触发页面跳转
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
break;
}
}
//取消监听后操作
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
//取消声音
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();

@ -27,7 +27,7 @@ import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
//广播接收器用于响应来自其他应用程序或者系统的广播消息(可以使事件)
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
@ -39,31 +39,24 @@ public class AlarmInitReceiver extends BroadcastReceiver {
private static final int COLUMN_ALERTED_DATE = 1;
@Override
public void onReceive(Context context, Intent intent) { //设置接收装置
//获取当前时间的常用方法。它返回自 1970 年 1 月 1 日 00:00:00.000 到当前时刻的时间距离
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();
//查询未到响应时间且是有提醒类型的便签
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);
if (c != null) {
if (c.moveToFirst()) {
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
//显示intent格式明确启动应用中的AlarmReceiver类
Intent sender = new Intent(context, AlarmReceiver.class);
//指定要访问的资源URI(这里的uir是把id和contentUri连接后成一个新的Uri)
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
//(满足特定条件但这里是0即没有)向BroadcastReceiver发送执行sender的广播
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
//获取 AlarmManager
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
//set(int typelong startTimePendingIntent pi) ,设置一次性定时器,到达时间执行完就没了
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
//闹钟在睡眠状态下会唤醒系统并执行提示功能
} while (c.moveToNext());
}
c.close();

@ -20,11 +20,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {//闹钟响应广播接收者
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class); //启动闹钟响应的行为
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//启动一个新的任务栈,为何要启动新的???
context.startActivity(intent);//执行
intent.setClass(context, AlarmAlertActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

@ -15,65 +15,68 @@ import net.micode.notes.R;
import java.io.InputStream;
public class BackgroundManager {
public static final int REQUEST_CODE_PICK_IMAGE = 2001;
private static final String PREF_BG_TYPE = "pref_bg_type"; // 0=color,1=builtin,2=uri
private static final String PREF_BG_COLOR = "pref_bg_color";
private static final String PREF_BG_RES_ID = "pref_bg_res_id";
private static final String PREF_BG_URI = "pref_bg_uri";
private static final int BG_TYPE_COLOR = 0;
private static final int BG_TYPE_BUILTIN = 1;
private static final int BG_TYPE_URI = 2;
public static final int REQUEST_CODE_PICK_IMAGE = 2001; // 选图请求码
private static final String PREF_BG_TYPE = "pref_bg_type"; // 背景类型key0=颜色1=内置资源2=自定义图片URI
private static final String PREF_BG_COLOR = "pref_bg_color"; // 背景颜色key
private static final String PREF_BG_RES_ID = "pref_bg_res_id"; // 内置背景资源ID key
private static final String PREF_BG_URI = "pref_bg_uri"; // 自定义图片URI key
private static final int BG_TYPE_COLOR = 0; // 颜色类型标识
private static final int BG_TYPE_BUILTIN = 1; // 内置资源类型标识
private static final int BG_TYPE_URI = 2; // 自定义图片类型标识
private Activity mActivity;
private View mRootView;
private View mRootView;//要设置背景的根View
public BackgroundManager(Activity activity, int rootViewId) {
mActivity = activity;
mRootView = activity.findViewById(rootViewId);
mRootView = activity.findViewById(rootViewId);//绑定指定的根View
if (mRootView == null) {
mRootView = activity.getWindow().getDecorView();
mRootView = activity.getWindow().getDecorView();//若传入的View ID无效取窗口根View
}
}
// 根据偏好设置应用背景
public void applyBackgroundFromPrefs() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
int type = sp.getInt(PREF_BG_TYPE, -1);
if (type == BG_TYPE_COLOR) {
int color = sp.getInt(PREF_BG_COLOR, 0xFFFFFFFF);
applyColorBackground(color);
} else if (type == BG_TYPE_BUILTIN) {
int resId = sp.getInt(PREF_BG_RES_ID, R.drawable.list_background);
applyBuiltinBackground(resId);
} else if (type == BG_TYPE_URI) {
String uriStr = sp.getString(PREF_BG_URI, null);
int type = sp.getInt(PREF_BG_TYPE, -1);//读取背景类型
if (type == BG_TYPE_COLOR) {//颜色类型
int color = sp.getInt(PREF_BG_COLOR, 0xFFFFFFFF);//读取颜色值
applyColorBackground(color);//应用颜色背景
} else if (type == BG_TYPE_BUILTIN) {//内置资源类型
int resId = sp.getInt(PREF_BG_RES_ID, R.drawable.list_background);//读取资源ID
applyBuiltinBackground(resId);//应用内置背景
} else if (type == BG_TYPE_URI) {//自定义图片类型
String uriStr = sp.getString(PREF_BG_URI, null);//读取URI字符串
if (uriStr != null) {
applyUriBackground(Uri.parse(uriStr));
applyUriBackground(Uri.parse(uriStr));//应用自定义图片背景
}
} else {
mRootView.setBackgroundResource(R.drawable.list_background);
mRootView.setBackgroundResource(R.drawable.list_background);//默认背景
}
}
// 应用颜色背景并保存偏好设置
public void applyColorAndSave(int color) {
applyColorBackground(color);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_COLOR).putInt(PREF_BG_COLOR, color).commit();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);//获取默认SharedPreferences
sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_COLOR).putInt(PREF_BG_COLOR, color).commit();//保存背景类型和颜色值
}
// 应用内置背景并保存偏好设置
public void applyBuiltinAndSave(int resId) {
applyBuiltinBackground(resId);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_BUILTIN).putInt(PREF_BG_RES_ID, resId).commit();
}
// 启动图库选择图片
public void pickImageFromGallery() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
mActivity.startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
}
// 处理图库选择结果
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
Uri uri = data.getData();
@ -85,6 +88,7 @@ public class BackgroundManager {
return false;
}
// 重置为默认背景并清除偏好设置
public void resetToDefaultAndClear() {
mRootView.setBackgroundResource(R.drawable.list_background);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
@ -99,9 +103,10 @@ public class BackgroundManager {
mRootView.setBackgroundResource(resId);
}
// 应用URI背景
private void applyUriBackground(Uri uri) {
try {
InputStream is = mActivity.getContentResolver().openInputStream(uri);
InputStream is = mActivity.getContentResolver().openInputStream(uri);//通过内容解析器打开URI对应的输入流
if (is != null) {
Drawable d = Drawable.createFromStream(is, uri.toString());
mRootView.setBackgroundDrawable(d);
@ -113,6 +118,7 @@ public class BackgroundManager {
}
}
// 应用URI背景并保存偏好设置
private void applyUriAndSave(Uri uri) {
try {
InputStream is = mActivity.getContentResolver().openInputStream(uri);

@ -39,6 +39,7 @@ public class CallRecordReceiver extends BroadcastReceiver {
Log.d(TAG, "Received broadcast: " + intent.getAction());
if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);

@ -87,7 +87,7 @@ public class CallRecordService extends IntentService {
// 使用正确的键名传递便签ID
intent.putExtra(Intent.EXTRA_UID, noteId);
intent.putExtra(Notes.INTENT_EXTRA_CALL_DATE, callDate);
intent.putExtra("phone_number", phoneNumber);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber);
// 从服务启动 Activity需要 NEW_TASK 标志;避免清除整个任务栈
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {

@ -27,7 +27,7 @@ import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
//FrameLayout 是 Android 布局容器之一,用于在屏幕上开辟一块区域,将子控件按照添加顺序进行层叠显示
public class DateTimePicker extends FrameLayout {
private static final boolean DEFAULT_ENABLE_STATE = true;
@ -45,56 +45,56 @@ public class DateTimePicker extends FrameLayout {
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;
//XX选择器设置
//这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午)
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];//
private boolean mIsAm; //12小时制是否上午
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;//24小时制
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; //是否启动?
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising; //是否正在初始化?
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener; //自定义接口,创建接口类型变量,外部实现功能可以调用onDateTimeChanged了
//系统接口,自己实现功能,当用户滑动日期滚轮(比如从 5 号滑到 6 号系统会自动调用onValueChange方法
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { //日期变更
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();//更新日期
onDateTimeChanged();//触发自定义接口的回调(把事件传给外部)[实现方法不在此类],作用未知??
updateDateControl();
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { //小时变更
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance(); //获取现在的日历
if (!mIs24HourView) { //12小时制
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { //从十一点多调整到第二天0点日期加一。
cal.setTimeInMillis(mDate.getTimeInMillis());// set time to 1970 年 1 月 1 日的 00:00:00.000到Calendar对象表示的时间之间的毫秒数 after january 1 1970
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) {//从0点多调整到昨天点日期加一。
} 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 || //上下午切换
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 {//24小时制
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {//同上面日期切换逻辑
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
@ -104,8 +104,8 @@ public class DateTimePicker extends FrameLayout {
isDateChanged = true;
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);//24小时制的小时时间数
mDate.set(Calendar.HOUR_OF_DAY, newHour); //通过set函数将新的Hour值传给mDate
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));
@ -115,18 +115,18 @@ public class DateTimePicker extends FrameLayout {
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {//分钟变更
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; //分钟导致的的小时切换
int offset = 0;
if (oldVal == maxValue && newVal == minValue) {
offset += 1; //小时加一
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1; //小时减一
offset -= 1;
}
if (offset != 0) { //更新小时时间
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
@ -139,44 +139,44 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);//通过set函数将新的minutes值传给mDate
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {//上下午变更
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;//为啥要变成否定的
if (mIsAm) {//下午变成上午了24小时制减去12
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {//上午变下午就加12除余
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 {//接口要求实现onDateTimeChanged
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {// 获得1970 年 1 月 1 日的 00:00:00.000到Calendar对象表示的时间之间的毫秒数
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));//把上一个函数的毫秒改成24小时制的日期格式
this(context, date, DateFormat.is24HourFormat(context));
}
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);//super是啥
super(context);
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);//inflate的作用
inflate(context, R.layout.datetime_picker, this);
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
@ -188,14 +188,14 @@ public class DateTimePicker extends FrameLayout {
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);//
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);//设置展示的字符串
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
@ -208,7 +208,7 @@ public class DateTimePicker extends FrameLayout {
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());//?
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
@ -241,7 +241,6 @@ public class DateTimePicker extends FrameLayout {
return mDate.getTimeInMillis();
}
//两种不同的参数,对应不同的设置现在时间的方法
/**
* Set the current date
*
@ -427,87 +426,58 @@ public class DateTimePicker extends FrameLayout {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView; //spinner拉条 就是选am还是pm时候用的
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
//
/**
*
*
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 计算起始日期当前日期减去3天DAYS_IN_ALL_WEEK/2=3再减1天得到一周前的日期
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
// 每次循环增加一天,生成一周内的日期
cal.add(Calendar.DAY_OF_YEAR, 1);
// 格式化日期为"MM.dd EEEE"格式例如05.20 星期一)
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
// 设置日期显示值到选择器
mDateSpinner.setDisplayedValues(mDateDisplayValues);
// 将选择器默认值设置为中间位置(即当前日期)
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
// 刷新选择器显示
mDateSpinner.invalidate();
}
/**
* /
* 24/
*/
private void updateAmPmControl() {
if (mIs24HourView) {
// 24小时制下隐藏上午/下午选择器
mAmPmSpinner.setVisibility(View.GONE);
} else {
// 12小时制下根据当前是上午还是下午设置选择器值
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
// 显示上午/下午选择器
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
/**
*
* 24
*/
private void updateHourControl() {
if (mIs24HourView) {
// 24小时制小时范围0-23
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 12小时制小时范围1-12
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
*
*
* @param callback null
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
/**
*
*
*/
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
// 调用监听器的onDateTimeChanged方法传递当前选择的年、月、日、时、分
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}

@ -28,39 +28,37 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
//对话框弹出
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener; //设置时间的接口
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
public interface OnDateTimeSetListener {//实现接口的函数调用
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
super(context);//访问AlertDialog的context
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);//自定义对话框
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { //注册监听器,用户操作日期时间选择器时,这个监听器会被自动触发
setView(mDateTimePicker);
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());//根据新的日期时间更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
});
//下面三行是日期时间选择器初始化的核心逻辑
mDate.setTimeInMillis(date); //导入calendar时间
mDate.set(Calendar.SECOND, 0);//秒数设为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());//设置为当前时间
setButton(context.getString(R.string.datetime_dialog_ok), this);//设置了确认和取消按钮
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
set24HourView(DateFormat.is24HourFormat(this.getContext()));
updateTitle(mDate.getTimeInMillis());
@ -70,21 +68,20 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { //设置时间的接口
mOnDateTimeSetListener = callBack;
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
private void updateTitle(long date) {
int flag = //亮点,位掩码设计 返回了结果是一个包含所有标志位的整数作为时间显示格式
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
//再并上24小时格式或12小时格式
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR; //错误应为12HOUR
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); //设置对话框标题
} //日期时间的本地化格式化
public void onClick(DialogInterface arg0, int arg1) {//点击确认按钮时调用
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}

@ -29,35 +29,33 @@ import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu; //弹窗菜单
private PopupMenu mPopupMenu;
private Menu mMenu;
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);//绘制下拉菜单图标
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();//获取弹窗菜单的菜单对象
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);//获得菜单
//第一个参数用于指定我们通过哪一个资源文件来创建菜单menuId。
// 第二个参数用于指定我们的菜单项将添加到哪一个 Meau 对象中
mButton.setOnClickListener(new OnClickListener() {//设置下拉菜单图标点击事件
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {//设置下拉菜单选项点击事件监听
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
public MenuItem findItem(int id) {//根据菜单项的ID查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
public void setTitle(CharSequence title) {//设置下拉菜单图标上的文本
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -28,8 +28,8 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
//将cursor对象数据加载到界面的适配器
public class FoldersListAdapter extends CursorAdapter {//将文件夹数据转换成视图
public class FoldersListAdapter extends CursorAdapter {
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
@ -39,43 +39,41 @@ public class FoldersListAdapter extends CursorAdapter {//将文件夹数据转
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);//调用父类构造方法,初始化上下文和游标
super(context, c);
// TODO Auto-generated constructor stub
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {//创建新的视图
return new FolderListItem(context);//创建一个新的FolderListItem视图
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) {//判断视图是否为FolderListItem类型
//根据文件夹类型动态显示不同的名称,当文件夹是文件夹时,显示移动到父文件夹
//当文件夹是 普通文件夹时,显示其实际名称
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);//将文件夹名称绑定到FolderListItem视图
((FolderListItem) view).bind(folderName);
}
}
public String getFolderName(Context context, int position) {//根据位置找到对应文件夹的名称
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);
}
//布局管理器 之一,属于 ViewGroup 的子类,用于 将子View按照线性方向排列
private class FolderListItem extends LinearLayout {//文件夹列表项视图
private class FolderListItem extends LinearLayout {
private TextView mName;
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);//对 LayoutInflater.inflate()的简化
//将R.layout.folder_list_item布局文件实例化并添加到当前视图FolderListItem
mName = (TextView) findViewById(R.id.tv_folder_name);//获取布局文件中的TextView控件
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
public void bind(String name) {
mName.setText(name);//将文件夹名称设置到TextView中
mName.setText(name);
}
}

@ -0,0 +1,569 @@
/*
* 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.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.HashSet;
public class MemoryBottleDialog extends Dialog implements View.OnClickListener {
private static final String PREF_MEMORY_FOLDER_ID = "pref_memory_bottle_folder_id";
private static final List<MemoryEntry> sAllEntries = new ArrayList<>();
private static final List<MemoryEntry> sRemainingEntries = new ArrayList<>();
private static final Random sRandom = new Random();
private static long sFolderId = Long.MIN_VALUE;
private final Activity mActivity;
private Button mAddButton;
private Button mBrowseButton;
private long mMemoryFolderId = -1;
private boolean mEntriesLoaded;
private boolean mLoading;
private boolean mBrowseLoading;
private LoadTask mLoadTask;
private BrowseTask mBrowseTask;
private PendingAction mPendingAction = PendingAction.NONE;
public MemoryBottleDialog(Activity activity) {
super(activity, android.R.style.Theme_Light_NoTitleBar);
mActivity = activity;
}
@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.memory_bottle);
getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
initResources();
}
@Override
public void dismiss() {
if (mLoadTask != null) {
mLoadTask.cancel(true);
mLoadTask = null;
}
if (mBrowseTask != null) {
mBrowseTask.cancel(true);
mBrowseTask = null;
}
super.dismiss();
}
private void initResources() {
mAddButton = (Button) findViewById(R.id.btn_memory_add);
mBrowseButton = (Button) findViewById(R.id.btn_memory_browse);
mAddButton.setOnClickListener(this);
mBrowseButton.setOnClickListener(this);
updateButtonState();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_memory_add:
requestAction(PendingAction.ADD);
break;
case R.id.btn_memory_browse:
requestAction(PendingAction.BROWSE);
break;
default:
break;
}
}
private void requestAction(PendingAction action) {
if (mLoading || mBrowseLoading) {
Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
return;
}
if (mMemoryFolderId <= 0) {
mPendingAction = action;
Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
startLoadTask(action == PendingAction.BROWSE);
return;
}
if (action == PendingAction.BROWSE && !mEntriesLoaded) {
mPendingAction = action;
Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
startLoadTask(true);
return;
}
if (action == PendingAction.ADD) {
showAddDialog();
} else if (action == PendingAction.BROWSE) {
browseMemory();
}
}
private void startLoadTask(boolean loadEntries) {
if (mLoadTask != null) {
return;
}
setLoading(true);
mLoadTask = new LoadTask(this, loadEntries);
mLoadTask.execute();
}
private void setLoading(boolean loading) {
mLoading = loading;
updateButtonState();
}
private void setBrowseLoading(boolean loading) {
mBrowseLoading = loading;
updateButtonState();
}
private void updateButtonState() {
boolean enabled = !(mLoading || mBrowseLoading);
if (mAddButton != null) {
mAddButton.setEnabled(enabled);
}
if (mBrowseButton != null) {
mBrowseButton.setEnabled(enabled);
}
}
private void showAddDialog() {
final EditText editText = new EditText(mActivity);
int padding = (int) (mActivity.getResources().getDisplayMetrics().density * 16);
editText.setPadding(padding, padding, padding, padding);
editText.setGravity(Gravity.TOP | Gravity.START);
editText.setMinLines(4);
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
editText.setHint(R.string.memory_bottle_add_hint);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(R.string.memory_bottle_add_title);
builder.setView(editText);
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
final AlertDialog dialog = builder.show();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = editText.getText().toString().trim();
if (TextUtils.isEmpty(content)) {
Toast.makeText(mActivity, R.string.memory_bottle_empty_input,
Toast.LENGTH_SHORT).show();
return;
}
if (createMemoryNote(content)) {
dialog.dismiss();
}
}
});
}
private boolean createMemoryNote(String content) {
if (mMemoryFolderId <= 0) {
Toast.makeText(mActivity, R.string.memory_bottle_folder_error, Toast.LENGTH_SHORT).show();
return false;
}
WorkingNote note = WorkingNote.createEmptyNote(mActivity, mMemoryFolderId,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.getDefaultBgId(mActivity));
note.setWorkingText(content);
if (!note.saveNote()) {
Toast.makeText(mActivity, R.string.memory_bottle_save_failed, Toast.LENGTH_SHORT).show();
return false;
}
long noteId = note.getNoteId();
long createdDate = queryNoteCreatedDate(noteId);
MemoryEntry entry = new MemoryEntry(noteId, createdDate, content);
sAllEntries.add(entry);
sRemainingEntries.add(entry);
mEntriesLoaded = true;
Toast.makeText(mActivity, R.string.memory_bottle_save_success, Toast.LENGTH_SHORT).show();
return true;
}
private long queryNoteCreatedDate(long noteId) {
Cursor cursor = mActivity.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
new String[] { NoteColumns.CREATED_DATE },
null, null, null);
if (cursor == null) {
return System.currentTimeMillis();
}
try {
if (cursor.moveToFirst()) {
return cursor.getLong(0);
}
} finally {
cursor.close();
}
return System.currentTimeMillis();
}
private void browseMemory() {
if (sAllEntries.isEmpty()) {
Toast.makeText(mActivity, R.string.memory_bottle_empty, Toast.LENGTH_SHORT).show();
return;
}
if (sRemainingEntries.isEmpty()) {
showBrowseFinishedDialog();
return;
}
showRandomEntry();
}
private void showBrowseFinishedDialog() {
new AlertDialog.Builder(mActivity)
.setMessage(R.string.memory_bottle_browse_done)
.setPositiveButton(R.string.memory_bottle_restart, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
resetRemainingEntries();
showRandomEntry();
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void resetRemainingEntries() {
sRemainingEntries.clear();
sRemainingEntries.addAll(sAllEntries);
}
private void showRandomEntry() {
int index = sRandom.nextInt(sRemainingEntries.size());
MemoryEntry entry = sRemainingEntries.remove(index);
startBrowseTask(entry);
}
private void startBrowseTask(MemoryEntry entry) {
if (mBrowseTask != null) {
return;
}
setBrowseLoading(true);
mBrowseTask = new BrowseTask(this, entry);
mBrowseTask.execute();
}
private String formatEntryMessage(long createdDate, String content) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
String date = format.format(new Date(createdDate));
return mActivity.getString(R.string.memory_bottle_entry_format, date, content);
}
private long ensureMemoryFolder() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
long storedId = sp.getLong(PREF_MEMORY_FOLDER_ID, Long.MIN_VALUE);
if (storedId > 0 && DataUtils.visibleInNoteDatabase(mActivity.getContentResolver(),
storedId, Notes.TYPE_FOLDER)) {
return storedId;
}
String folderName = mActivity.getString(R.string.memory_bottle_folder_name);
long folderId = queryFolderIdByName(folderName);
if (folderId > 0) {
sp.edit().putLong(PREF_MEMORY_FOLDER_ID, folderId).commit();
return folderId;
}
folderId = createMemoryFolder(folderName);
if (folderId > 0) {
sp.edit().putLong(PREF_MEMORY_FOLDER_ID, folderId).commit();
return folderId;
}
return -1;
}
private long queryFolderIdByName(String name) {
ContentResolver resolver = mActivity.getContentResolver();
Cursor cursor = resolver.query(
Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.ID },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?"
+ " AND " + NoteColumns.SNIPPET + "=?",
new String[] { String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER), name },
null);
if (cursor == null) {
return 0;
}
try {
if (cursor.moveToFirst()) {
return cursor.getLong(0);
}
} finally {
cursor.close();
}
return 0;
}
private long createMemoryFolder(String name) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.PARENT_ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
android.net.Uri uri = mActivity.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
if (uri == null) {
return 0;
}
try {
return Long.parseLong(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
return 0;
}
}
private String queryNoteContent(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(
Notes.CONTENT_DATA_URI,
new String[] { DataColumns.CONTENT },
DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?",
new String[] { String.valueOf(noteId), TextNote.CONTENT_ITEM_TYPE },
null);
if (cursor == null) {
return "";
}
try {
if (cursor.moveToFirst()) {
return cursor.getString(0);
}
} finally {
cursor.close();
}
return "";
}
private List<MemoryEntry> loadEntriesFromDatabase(long folderId) {
List<MemoryEntry> entries = new ArrayList<>();
ContentResolver resolver = mActivity.getContentResolver();
Cursor cursor = resolver.query(
Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.ID, NoteColumns.CREATED_DATE },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(Notes.TYPE_NOTE), String.valueOf(folderId) },
NoteColumns.CREATED_DATE + " DESC");
if (cursor == null) {
return entries;
}
try {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(0);
long createdDate = cursor.getLong(1);
entries.add(new MemoryEntry(noteId, createdDate, ""));
}
} finally {
cursor.close();
}
return entries;
}
private static final class MemoryEntry {
private final long id;
private final long createdDate;
private final String content;
private MemoryEntry(long id, long createdDate, String content) {
this.id = id;
this.createdDate = createdDate;
this.content = content;
}
}
private static final class LoadResult {
private final long folderId;
private final List<MemoryEntry> entries;
private final boolean loadedEntries;
private LoadResult(long folderId, List<MemoryEntry> entries, boolean loadedEntries) {
this.folderId = folderId;
this.entries = entries;
this.loadedEntries = loadedEntries;
}
}
private static final class LoadTask extends AsyncTask<Void, Void, LoadResult> {
private final WeakReference<MemoryBottleDialog> mRef;
private final boolean mLoadEntries;
private LoadTask(MemoryBottleDialog dialog, boolean loadEntries) {
mRef = new WeakReference<>(dialog);
mLoadEntries = loadEntries;
}
@Override
protected LoadResult doInBackground(Void... params) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null) {
return null;
}
long folderId = dialog.ensureMemoryFolder();
List<MemoryEntry> entries = new ArrayList<>();
if (folderId > 0 && mLoadEntries) {
entries = dialog.loadEntriesFromDatabase(folderId);
}
return new LoadResult(folderId, entries, mLoadEntries);
}
@Override
protected void onPostExecute(LoadResult result) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null || !dialog.isShowing()) {
return;
}
dialog.mLoadTask = null;
dialog.setLoading(false);
if (result == null || result.folderId <= 0) {
Toast.makeText(dialog.mActivity, R.string.memory_bottle_folder_error,
Toast.LENGTH_SHORT).show();
dialog.mPendingAction = PendingAction.NONE;
return;
}
dialog.mMemoryFolderId = result.folderId;
sFolderId = result.folderId;
if (result.loadedEntries) {
sAllEntries.clear();
sAllEntries.addAll(result.entries);
sRemainingEntries.clear();
sRemainingEntries.addAll(result.entries);
dialog.mEntriesLoaded = true;
} else if (sFolderId == result.folderId) {
dialog.mEntriesLoaded = !sAllEntries.isEmpty();
}
PendingAction pending = dialog.mPendingAction;
dialog.mPendingAction = PendingAction.NONE;
if (pending == PendingAction.ADD) {
dialog.showAddDialog();
} else if (pending == PendingAction.BROWSE) {
dialog.browseMemory();
}
}
}
private static final class BrowseResult {
private final MemoryEntry entry;
private final String content;
private BrowseResult(MemoryEntry entry, String content) {
this.entry = entry;
this.content = content;
}
}
private static final class BrowseTask extends AsyncTask<Void, Void, BrowseResult> {
private final WeakReference<MemoryBottleDialog> mRef;
private final MemoryEntry mEntry;
private BrowseTask(MemoryBottleDialog dialog, MemoryEntry entry) {
mRef = new WeakReference<>(dialog);
mEntry = entry;
}
@Override
protected BrowseResult doInBackground(Void... params) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null) {
return null;
}
String content = mEntry.content;
if (TextUtils.isEmpty(content)) {
content = dialog.queryNoteContent(dialog.mActivity.getContentResolver(), mEntry.id);
}
if (TextUtils.isEmpty(content)) {
content = dialog.mActivity.getString(R.string.memory_bottle_missing_content);
}
return new BrowseResult(mEntry, content);
}
@Override
protected void onPostExecute(BrowseResult result) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null || !dialog.isShowing()) {
return;
}
dialog.mBrowseTask = null;
dialog.setBrowseLoading(false);
if (result == null) {
return;
}
String message = dialog.formatEntryMessage(result.entry.createdDate, result.content);
new AlertDialog.Builder(dialog.mActivity)
.setTitle(R.string.memory_bottle_title)
.setMessage(message)
.setPositiveButton(R.string.memory_bottle_close, null)
.setNegativeButton(R.string.memory_bottle_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
dialog.deleteMemoryEntry(result.entry);
}
})
.show();
}
}
private void deleteMemoryEntry(MemoryEntry entry) {
if (mMemoryFolderId <= 0) {
Toast.makeText(mActivity, R.string.memory_bottle_folder_error, Toast.LENGTH_SHORT).show();
return;
}
HashSet<Long> ids = new HashSet<Long>();
ids.add(entry.id);
if (DataUtils.batchMoveToTrash(mActivity.getContentResolver(), ids, mMemoryFolderId)) {
sAllEntries.remove(entry);
sRemainingEntries.remove(entry);
Toast.makeText(mActivity, R.string.memory_bottle_delete_success, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mActivity, R.string.memory_bottle_delete_failed, Toast.LENGTH_SHORT).show();
}
}
private enum PendingAction {
NONE,
ADD,
BROWSE
}
}

@ -37,117 +37,80 @@ import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
/**
* AndroidEditText
* /
*/
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText"; // 日志标签
private int mIndex; // 当前编辑框在容器中的索引位置
private int mSelectionStartBeforeDelete; // 删除操作前的光标起始位置
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:" ; // HTTP协议
private static final String SCHEME_EMAIL = "mailto:" ; // 邮件协议
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); // 邮件链接菜单
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);
}
/**
* NoteEditActivity
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
*
* @param index
* @param text
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
*
* @param index
* @param text
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
*
* @param index
* @param hasText
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener; // 文本变化监听器实例
private OnTextViewChangeListener mOnTextViewChangeListener;
//新增选区变化回调接口
private OnSelectionChangeListener mOnSelectionChangeListener;
/**
* NoteEditText
* @param context
*/
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
/**
*
* @param index
*/
public void setIndex(int index) {
mIndex = index;
}
/**
* NoteEditActivity
* @param listener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
//新增选区变化回调接口设置方法
public void setOnSelectionChangeListener(OnSelectionChangeListener listener) {
mOnSelectionChangeListener = listener;
}
/**
* XMLNoteEditText
* @param context
* @param attrs XML
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
/**
* XMLNoteEditText
* @param context
* @param attrs XML
* @param defStyle
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
/**
*
* @param event
* @return
*/
@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();
@ -158,30 +121,22 @@ public class NoteEditText extends EditText {
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off); // 设置光标位置
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 回车键按下时交由onKeyUp处理
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除前的光标位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
@ -190,17 +145,10 @@ public class NoteEditText extends EditText {
return super.onKeyDown(keyCode, event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@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());
@ -211,14 +159,10 @@ public class NoteEditText extends EditText {
}
break;
case KeyEvent.KEYCODE_ENTER:
// 回车键处理:在当前位置分割文本,创建新的编辑框
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
// 获取光标后的文本
String text = getText().subSequence(selectionStart, length()).toString();
// 保留光标前的文本
setText(getText().subSequence(0, selectionStart));
// 通知Activity添加新的编辑框
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
@ -230,16 +174,9 @@ public class NoteEditText extends EditText {
return super.onKeyUp(keyCode, event);
}
/**
*
* @param focused
* @param direction
* @param previouslyFocusedRect
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
// 失去焦点且文本为空时通知Activity隐藏相关选项
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
@ -249,10 +186,8 @@ public class NoteEditText extends EditText {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
*
* @param menu
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
@ -262,10 +197,8 @@ public class NoteEditText extends EditText {
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 查找选中区域内的URLSpan
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) {
@ -278,11 +211,10 @@ public class NoteEditText extends EditText {
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;
}
@ -306,4 +238,4 @@ public class NoteEditText extends EditText {
// 新增选区变化回调接口
interface OnSelectionChangeListener {
void onSelectionChanged(int index, int selStart, int selEnd);
}
}

@ -0,0 +1,240 @@
/*
* 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.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.IS_HABIT,
};
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private static final int IS_HABIT_COLUMN = 12;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private boolean mIsHabit;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) {
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 移除HTML标签
mSnippet = mSnippet.replaceAll("<[^>]*>", "");
// 移除CHECKED和UNCHECKED标签
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
// habit flag, may not exist on older DBs
try {
mIsHabit = cursor.getInt(IS_HABIT_COLUMN) > 0;
} catch (Exception e) {
mIsHabit = false;
}
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
if (mName == null) {
mName = "";
}
checkPostion(cursor);
}
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
mIsOneNoteFollowingFolder = true;
}
}
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isHabit() {
return mIsHabit;
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -54,6 +54,7 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
@ -114,7 +115,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private enum ListEditState {
NOTE_LIST, // 根目录笔记列表
SUB_FOLDER, // 子文件夹笔记列表
CALL_RECORD_FOLDER // 通话记录文件夹列表
CALL_RECORD_FOLDER, // 通话记录文件夹列表
TRASH_FOLDER // 回收站文件夹列表
};
private ListEditState mState; // 当前列表状态
@ -133,6 +135,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private static final String TAG = "NotesListActivity"; // 日志标签
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // ListView滚动速率
private NoteItemData mFocusNoteDataItem; // 当前聚焦的笔记数据项
private Button mMemoryBottle; // 记忆瓶按钮
private ImageButton mTrashButton; // 回收站按钮
private TrashManager mTrashManager; // 回收站管理器
private EncryptedFolderManager mEncryptedFolderManager; // 加密文件夹管理器
private View mNotesRootView; // 笔记根视图
private BackgroundManager mBackgroundManager; // 背景管理器
private MemoryBottleDialog mMemoryBottleDialog; // 记忆瓶对话框
/**
*
@ -141,13 +150,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
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)"; // 根文件夹查询条件
+ NoteColumns.NOTES_COUNT + ">0)";
/**
* startActivityForResult
*/
private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开笔记的请求码
private final static int REQUEST_CODE_NEW_NODE = 103; // 新建笔记的请求码
private static final String PREF_MEMORY_FOLDER_ID = "pref_memory_bottle_folder_id";
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
@Override
protected void onCreate(Bundle savedInstanceState) {// 初始化活动,设置布局和资源
@ -262,12 +270,61 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
mAddNewNote.setOnClickListener(this);
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
mMemoryBottle = (Button) findViewById(R.id.btn_memory_bottle);
mMemoryBottle.setOnClickListener(this);
mTrashButton = (ImageButton) findViewById(R.id.btn_trash);
mTrashButton.setOnClickListener(this);
mDispatch = false;
mDispatchY = 0;
mOriginY = 0;
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mState = ListEditState.NOTE_LIST;
mModeCallBack = new ModeCallback();
mTrashManager = new TrashManager(this, mContentResolver, new TrashManager.Callback() {
@Override
public void onWidgetsNeedUpdate(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
}
@Override
public void onListChanged() {
startAsyncNotesListQuery();
}
@Override
public void onActionModeFinished() {
mModeCallBack.finishActionMode();
}
@Override
public void onRestoreInvalid() {
Toast.makeText(NotesListActivity.this, R.string.trash_restore_invalid,
Toast.LENGTH_SHORT).show();
}
});
mEncryptedFolderManager = new EncryptedFolderManager(this, mContentResolver,
new EncryptedFolderManager.Callback() {
@Override
public void onEncryptedFolderCreated() {
startAsyncNotesListQuery();
}
@Override
public void onEncryptedFolderUnlocked(NoteItemData data) {
openFolderInternal(data);
}
});
updateMemoryButtonVisibility();
updateTrashButtonVisibility();
//初始化背景管理器
mNotesRootView = findViewById(R.id.notes_root);
@ -439,6 +496,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private ActionMode mActionMode;
/** 移动菜单项,用于批量移动笔记 */
private MenuItem mMoveMenu;
/** 恢复菜单项,用于从回收站恢复笔记 */
private MenuItem mRestoreMenu;
/**
*
@ -448,19 +507,37 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu);
MenuItem deleteMenu = menu.findItem(R.id.delete);
deleteMenu.setOnMenuItemClickListener(this);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mRestoreMenu = menu.findItem(R.id.restore);
if (mState == ListEditState.TRASH_FOLDER) {
mMoveMenu.setVisible(false);
mRestoreMenu.setVisible(true);
mRestoreMenu.setOnMenuItemClickListener(this);
deleteMenu.setTitle(R.string.menu_delete_permanent);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
mRestoreMenu.setVisible(false);
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
deleteMenu.setTitle(R.string.menu_delete);
}
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true);
mNotesListAdapter.setChoiceMode(true, mState == ListEditState.TRASH_FOLDER);
mNotesListView.setLongClickable(false);
mAddNewNote.setVisibility(View.GONE);
if (mMemoryBottle != null) {
mMemoryBottle.setVisibility(View.GONE);
}
if (mTrashButton != null) {
mTrashButton.setVisibility(View.GONE);
}
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
@ -509,7 +586,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public void onDestroyActionMode(ActionMode mode) {// 多选模式结束时的操作
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
if (mState == ListEditState.CALL_RECORD_FOLDER) {
mAddNewNote.setVisibility(View.GONE);
} else {
mAddNewNote.setVisibility(View.VISIBLE);
}
updateMemoryButtonVisibility();
updateTrashButtonVisibility();
}
public void finishActionMode() {// 结束多选模式
@ -534,21 +617,29 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.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_notes,
mNotesListAdapter.getSelectedCount()));
if (mState == ListEditState.TRASH_FOLDER) {
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
} else {
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
}
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete();
}
});
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
startQueryDestinationFolders();
break;
case R.id.restore:
restoreSelected();
break;
default:
return false;
}
@ -558,6 +649,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private class NewNoteOnTouchListener implements OnTouchListener {// 处理"新建笔记"按钮的触摸事件
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && !mDispatch) {
// 当触摸结束且没有分发到列表视图时,直接触发点击事件
v.performClick();
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Display display = getWindowManager().getDefaultDisplay();
@ -593,7 +690,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
mDispatch = false;
return false;
}
case MotionEvent.ACTION_MOVE: {// 处理"新建笔记"按钮的移动事件
if (mDispatch) {
@ -601,20 +699,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
return false;
}
default: {// 处理"新建笔记"按钮的其他事件
default: {
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
boolean result = mNotesListView.dispatchTouchEvent(event);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
return result;
}
break;
return false;
}
}
return false;
}
};
/**
@ -624,10 +721,24 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
String[] selectionArgs;
if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) {
long memoryFolderId = getMemoryBottleFolderId();
if (memoryFolderId > 0) {
selection = "(" + selection + ") AND " + NoteColumns.ID + "<> ?";
selectionArgs = new String[] {
String.valueOf(mCurrentFolderId),
String.valueOf(memoryFolderId)
};
} else {
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
}
} else {
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
}
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC" );// 按类型和修改日期排序
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs,
NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
/**
@ -699,6 +810,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
HashSet<Long> ids = mNotesListAdapter.getSelectedItemIds();
boolean inTrash = mState == ListEditState.TRASH_FOLDER;
mTrashManager.batchDelete(inTrash, ids, widgets, mCurrentFolderId);
if (!isSyncMode()) {
// if not synced, delete notes directly
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
@ -731,6 +845,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}.execute();
}
private void restoreSelected() {
HashSet<Long> ids = mNotesListAdapter.getSelectedItemIds();
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
mTrashManager.restoreSelected(ids, widgets);
}
private void deleteFolder(long folderId) {// 删除文件夹
if (folderId == Notes.ID_ROOT_FOLDER) {
@ -758,7 +877,38 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
}
private void updateMemoryButtonVisibility() {
if (mMemoryBottle == null) {
return;
}
if (mState == ListEditState.NOTE_LIST) {
mMemoryBottle.setVisibility(View.VISIBLE);
} else {
mMemoryBottle.setVisibility(View.GONE);
}
}
private void updateTrashButtonVisibility() {
if (mTrashButton == null) {
return;
}
if (mState == ListEditState.NOTE_LIST) {
mTrashButton.setVisibility(View.VISIBLE);
} else {
mTrashButton.setVisibility(View.GONE);
}
}
private long getMemoryBottleFolderId() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
long folderId = sp.getLong(PREF_MEMORY_FOLDER_ID, -1);
if (folderId > 0 && DataUtils.visibleInNoteDatabase(mContentResolver, folderId,
Notes.TYPE_FOLDER)) {
return folderId;
}
return -1;
}
private void openNode(NoteItemData data) {// 打开笔记
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
@ -766,7 +916,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
private void openFolder(NoteItemData data) {// 打开文件夹
private void openFolder(NoteItemData data) {
if (data.getId() == Notes.ID_TRASH_FOLER) {
openTrashFolder();
return;
}
EncryptedFolderManager.EncryptedFolderInfo encryptedInfo =
mEncryptedFolderManager.getEncryptedFolderInfo(data.getId());
if (encryptedInfo != null) {
mEncryptedFolderManager.showEncryptedUnlockDialog(encryptedInfo, data);
return;
}
openFolderInternal(data);
}
private void openFolderInternal(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery();
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
@ -781,6 +945,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mTitleBar.setText(data.getSnippet());
}
mTitleBar.setVisibility(View.VISIBLE);
updateMemoryButtonVisibility();
updateTrashButtonVisibility();
}
public void onClick(View v) {
@ -788,11 +954,46 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
case R.id.btn_new_note:
createNewNote();
break;
case R.id.btn_memory_bottle:
openMemoryBottle();
break;
case R.id.btn_trash:
openTrashFolder();
break;
default:
break;
}
}
private void openMemoryBottle() {
if (mMemoryBottleDialog == null) {
mMemoryBottleDialog = new MemoryBottleDialog(this);
}
mMemoryBottleDialog.show();
}
private void openTrashFolder() {
mCurrentFolderId = Notes.ID_TRASH_FOLER;
mState = ListEditState.TRASH_FOLDER;
mTrashManager.cleanupExpiredTrash();
startAsyncNotesListQuery();
mTitleBar.setText(R.string.trash_folder_name);
mTitleBar.setVisibility(View.VISIBLE);
mAddNewNote.setVisibility(View.GONE);
updateMemoryButtonVisibility();
updateTrashButtonVisibility();
}
@Override
protected void onDestroy() {
if (mMemoryBottleDialog != null && mMemoryBottleDialog.isShowing()) {
mMemoryBottleDialog.dismiss();
}
mMemoryBottleDialog = null;
super.onDestroy();
}
private void showSoftInput() {// 显示软键盘
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
@ -901,6 +1102,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mState = ListEditState.NOTE_LIST;
mTitleBar.setVisibility(View.GONE);
startAsyncNotesListQuery();
updateMemoryButtonVisibility();
updateTrashButtonVisibility();
invalidateOptionsMenu();
break;
case CALL_RECORD_FOLDER:// 返回上一级通话记录文件夹
@ -909,8 +1112,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mAddNewNote.setVisibility(View.VISIBLE);
mTitleBar.setVisibility(View.GONE);
startAsyncNotesListQuery();
updateMemoryButtonVisibility();
updateTrashButtonVisibility();
break;
case NOTE_LIST:// 返回主界面
case TRASH_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
startAsyncNotesListQuery();
mTitleBar.setVisibility(View.GONE);
mAddNewNote.setVisibility(View.VISIBLE);
updateMemoryButtonVisibility();
updateTrashButtonVisibility();
break;
case NOTE_LIST:
super.onBackPressed();
break;
default:
@ -1002,6 +1216,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else if (mState == ListEditState.TRASH_FOLDER) {
getMenuInflater().inflate(R.menu.sub_folder, menu);
MenuItem newNote = menu.findItem(R.id.menu_new_note);
if (newNote != null) {
newNote.setVisible(false);
}
} else {
Log.e(TAG, "Wrong state:" + mState);
}
@ -1015,7 +1235,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
showCreateOrModifyFolderDialog(true);
break;
}
case R.id.menu_export_text: {// 导出笔记为文本文件
case R.id.menu_new_encrypted_folder: {
mEncryptedFolderManager.showCreateEncryptedFolderDialog();
break;
}
case R.id.menu_export_text: {
exportNoteToText();
break;
}
@ -1146,6 +1370,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
Log.e(TAG, "Wrong note type in SUB_FOLDER");
}
break;
case TRASH_FOLDER:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
} else {
Toast.makeText(NotesListActivity.this,
R.string.menu_restore, Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
@ -1175,7 +1407,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {// 列表项长按事件
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
if (mState == ListEditState.TRASH_FOLDER && !mNotesListAdapter.isInChoiceMode()) {
if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);

@ -31,80 +31,90 @@ import java.util.HashSet;
import java.util.Iterator;
public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和ListView之间的适配器将Cursor数据转换为ListView可以理解的视图格式
private static final String TAG = "NotesListAdapter"; // 日志标签
private Context mContext; // 上下文对象
private HashMap<Integer, Boolean> mSelectedIndex; // 选中项索引映射
private int mNotesCount; // 笔记数量(排除文件夹)
private boolean mChoiceMode; // 是否处于选择模式
public static class AppWidgetAttribute { // 小部件属性类用于存储小部件的ID和类型
public int widgetId; // 小部件ID
public int widgetType; // 小部件类型
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;
private boolean mIncludeFolders;
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
};
public NotesListAdapter(Context context) { // 构造函数创建NotesListAdapter实例
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>(); // 初始化选中项映射
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context); // 创建新的NotesListItem
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, // 绑定数据到视图
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(); // 通知数据变化刷新UI
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
public boolean isInChoiceMode() {
return mChoiceMode; // 检查是否处于选择模式
return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear(); // 清除之前的选中状态
mChoiceMode = mode; // 更新模式标志
mSelectedIndex.clear();
mChoiceMode = mode;
mIncludeFolders = false;
}
public void setChoiceMode(boolean mode, boolean includeFolders) {
mSelectedIndex.clear();
mChoiceMode = mode;
mIncludeFolders = includeFolders;
}
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); // 设置选中状态
int type = NoteItemData.getNoteType(cursor);
if (type == Notes.TYPE_NOTE || (mIncludeFolders && type == Notes.TYPE_FOLDER)) {
setCheckedItem(i, checked);
}
}
}
}
public HashSet<Long> getSelectedItemIds() {// 获取选中项的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); // 获取选中项的ID
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id); // 添加到集合
itemSet.add(id);
}
}
}
return itemSet;
}
public HashSet<AppWidgetAttribute> getSelectedWidget() {// 获取选中项的小部件属性集合
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
@ -112,10 +122,12 @@ public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和L
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId(); // 获取小部件ID
widget.widgetType = item.getWidgetType(); // 获取小部件类型
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
// Don't close cursor here, only the adapter could close it
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
@ -125,7 +137,7 @@ public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和L
return itemSet;
}
public int getSelectedCount() {// 获取选中项数量
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
@ -137,39 +149,39 @@ public class NotesListAdapter extends CursorAdapter {//Adapter作为Activity和L
count++;
}
}
return count; // 返回选中项数量
return count;
}
public boolean isAllSelected() {// 检查是否所有笔记都被选中
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
return (checkedCount != 0 && checkedCount == mNotesCount);
}
public boolean isSelectedItem(final int position) {// 检查指定位置的项是否被选中
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position); // 检查指定位置的项是否被选中
return mSelectedIndex.get(position);
}
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount(); // 当内容变化时,重新计算笔记数量
calcNotesCount();
}
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount(); // 当游标变化时,重新计算笔记数量
calcNotesCount();
}
private void 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) { // 只统计普通笔记,排除文件夹
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {

@ -31,33 +31,26 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 提醒图标(时钟或通话记录图标)
private TextView mTitle; // 笔记/文件夹标题或内容摘要
private TextView mTime; // 最后修改时间
private TextView mCallName; // 通话记录来电者名称(仅通话记录显示)
private NoteItemData mItemData; // 当前列表项的数据模型
private CheckBox mCheckBox; // 选择模式下的复选框(用于批量操作)
private ImageView mAlert;
private ImageView mHabit;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData;
private CheckBox mCheckBox;
/**
*
*/
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mHabit = (ImageView) findViewById(R.id.iv_habit_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);
}
/**
* UI
* @param data
* @param checked
*/
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);
@ -66,7 +59,6 @@ public class NotesListItem extends LinearLayout {
}
mItemData = data;
// 处理通话记录文件夹
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
@ -74,9 +66,14 @@ public class NotesListItem extends LinearLayout {
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) {
} else if (data.getId() == Notes.ID_TRASH_FOLER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.trash_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.baseline_restore_from_trash_24);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
@ -87,9 +84,13 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
}
// 处理普通文件夹和笔记
else {
// habit badge
if (data.isHabit()) {
mHabit.setVisibility(View.VISIBLE);
} else {
mHabit.setVisibility(View.GONE);
}
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@ -97,7 +98,14 @@ public class NotesListItem extends LinearLayout {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
// 加密文件夹使用锁图标,与打卡图标区分
// 通过DataUtils检查是否为加密文件夹
if (DataUtils.isEncryptedFolder(context.getContentResolver(), data.getId())) {
mAlert.setImageResource(R.drawable.lock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
@ -106,22 +114,21 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
if (data.isHabit()) {
mHabit.setVisibility(View.VISIBLE);
} else {
mHabit.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()) {
@ -132,14 +139,10 @@ public class NotesListItem extends LinearLayout {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
// 文件夹使用统一背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
/**
*
*/
public NoteItemData getItemData() {
return mItemData;
}

@ -48,37 +48,27 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/**
*
*
*/
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;
/** GTask同步广播接收器 */
private GTaskReceiver mReceiver;
/** 原始账户列表,用于检测是否添加了新账户 */
private Account[] mOriAccounts;
/** 是否添加了新账户的标志 */
private boolean mHasAddedAccount;
/**
*
* @param icicle
*/
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@ -94,16 +84,14 @@ public class NotesPreferenceActivity extends PreferenceActivity {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(mReceiver, filter);
registerReceiver(mReceiver, filter);
}
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
/**
* UI
*/
@Override
protected void onResume() {
super.onResume();
@ -132,9 +120,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
refreshUI();
}
/**
*
*/
@Override
protected void onDestroy() {
if (mReceiver != null) {
@ -143,9 +128,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
super.onDestroy();
}
/**
*
*/
private void loadAccountPreference() {
mAccountCategory.removeAll();
@ -176,9 +158,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
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);
@ -218,17 +197,11 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
* UI
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/**
* Google
*/
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
@ -285,9 +258,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
/**
*
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
@ -317,19 +287,11 @@ public class NotesPreferenceActivity extends PreferenceActivity {
dialogBuilder.show();
}
/**
* Google
* @return Google
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/**
*
* @param account
*/
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
@ -360,9 +322,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
*
*/
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
@ -375,7 +334,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit();
// clean up local gtask related info
new Thread(new Runnable() {//清空一系列数据thread多线程操作避免阻塞主线程
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
@ -385,20 +344,13 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}).start();
}
/**
*
* @return
*/
public static String getSyncAccountName(Context context) {//
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/**
*
*/
public static void setLastSyncTime(Context context, long time) {//
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
@ -406,20 +358,16 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit();
}
/**
*
* @return
*/
public static long getLastSyncTime(Context context) {//
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
private class GTaskReceiver extends BroadcastReceiver {// 处理同步状态更新广播
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {// 处理同步状态更新广播
public void onReceive(Context context, Intent intent) {
refreshUI();
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
@ -430,11 +378,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
*
* @param item
*/
public boolean onOptionsItemSelected(MenuItem item) {//
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, NotesListActivity.class);

@ -0,0 +1,192 @@
/*
* 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.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.HashSet;
public class TrashManager {
private static final String TAG = "TrashManager";
private final Context mContext;
private final ContentResolver mResolver;
private final Callback mCallback;
public interface Callback {
void onWidgetsNeedUpdate(HashSet<AppWidgetAttribute> widgets);
void onListChanged();
void onActionModeFinished();
void onRestoreInvalid();
}
public TrashManager(Context context, ContentResolver resolver, Callback callback) {
mContext = context;
mResolver = resolver;
mCallback = callback;
}
public void cleanupExpiredTrash() {
long expireTime = System.currentTimeMillis() - 24L * 60L * 60L * 1000L;
mResolver.delete(Notes.CONTENT_NOTE_URI,
NoteColumns.PARENT_ID + "=? AND " + NoteColumns.MODIFIED_DATE + "<?",
new String[] { String.valueOf(Notes.ID_TRASH_FOLER), String.valueOf(expireTime) });
}
public void batchDelete(final boolean inTrash, final HashSet<Long> ids,
final HashSet<AppWidgetAttribute> widgets, final long originFolderId) {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
if (inTrash) {
if (!DataUtils.batchDeleteNotes(mResolver, ids)) {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
if (!DataUtils.batchMoveToTrash(mResolver, ids, originFolderId)) {
Log.e(TAG, "Move notes to trash folder error");
}
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> resultWidgets) {
if (mCallback != null) {
mCallback.onWidgetsNeedUpdate(resultWidgets);
mCallback.onListChanged();
mCallback.onActionModeFinished();
}
}
}.execute();
}
public void restoreSelected(final HashSet<Long> ids, final HashSet<AppWidgetAttribute> widgets) {
new AsyncTask<Void, Void, Boolean>() {
protected Boolean doInBackground(Void... params) {
boolean hasInvalid = false;
long now = System.currentTimeMillis();
for (long id : ids) {
Cursor cursor = mResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
new String[] { NoteColumns.ORIGIN_PARENT_ID, NoteColumns.TYPE },
null, null, null);
if (cursor == null) {
continue;
}
long originParent = Notes.ID_ROOT_FOLDER;
int type = Notes.TYPE_NOTE;
try {
if (cursor.moveToFirst()) {
originParent = cursor.getLong(0);
type = cursor.getInt(1);
}
} finally {
cursor.close();
}
long targetParent = resolveRestoreParent(originParent);
if (targetParent != originParent) {
hasInvalid = true;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, targetParent);
values.put(NoteColumns.ORIGIN_PARENT_ID, 0);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, now);
mResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
values, null, null);
if (type == Notes.TYPE_FOLDER) {
restoreNotesForFolder(id, now);
}
}
return hasInvalid;
}
@Override
protected void onPostExecute(Boolean hasInvalid) {
if (mCallback != null) {
if (hasInvalid != null && hasInvalid) {
mCallback.onRestoreInvalid();
}
mCallback.onWidgetsNeedUpdate(widgets);
mCallback.onListChanged();
mCallback.onActionModeFinished();
}
}
}.execute();
}
public HashSet<AppWidgetAttribute> moveFolderToTrash(long folderId, long originFolderId) {
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mResolver, folderId);
DataUtils.moveNotesToTrashForFolder(mResolver, folderId);
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
if (!DataUtils.batchMoveToTrash(mResolver, ids, originFolderId)) {
Log.e(TAG, "Move folder to trash error");
}
return widgets;
}
private void restoreNotesForFolder(long folderId, long now) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, folderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, 0);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, now);
mResolver.update(Notes.CONTENT_NOTE_URI, values,
NoteColumns.PARENT_ID + "=? AND " + NoteColumns.ORIGIN_PARENT_ID + "=?",
new String[] { String.valueOf(Notes.ID_TRASH_FOLER), String.valueOf(folderId) });
}
private long resolveRestoreParent(long originParentId) {
if (originParentId == Notes.ID_ROOT_FOLDER || originParentId == Notes.ID_CALL_RECORD_FOLDER) {
return originParentId;
}
if (originParentId <= 0) {
return Notes.ID_ROOT_FOLDER;
}
Cursor cursor = mResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, originParentId),
new String[] { NoteColumns.ID, NoteColumns.PARENT_ID },
NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.ID_TRASH_FOLER) },
null);
if (cursor == null) {
return Notes.ID_ROOT_FOLDER;
}
try {
if (cursor.moveToFirst()) {
return originParentId;
}
} finally {
cursor.close();
}
return Notes.ID_ROOT_FOLDER;
}
}

@ -32,11 +32,11 @@ import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小部件提供器
public abstract class NoteWidgetProvider extends AppWidgetProvider {
public static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET//笔记摘要
NoteColumns.SNIPPET
};
public static final int COLUMN_ID = 0;
@ -46,9 +46,9 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小
private static final String TAG = "NoteWidgetProvider";
@Override
public void onDeleted(Context context, int[] appWidgetIds) {//删除小部件时调用
ContentValues values = new ContentValues();//创建内容值对象,用于更新数据库
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);//将小部件ID设置为无效值
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,
@ -59,18 +59,18 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,//查询投影,指定要返回的列
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);//false
update(context, appWidgetManager, appWidgetIds, false);
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {//更新小部件,是上面的实现方式
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
@ -90,38 +90,36 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {//笔记小
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);//设置意图操作,查看笔记
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);//设置意图操作,插入或编辑笔记
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
if (c != null) {
c.close();
}
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());//创建远程视图对象,用于更新小部件的布局
/*App Widget使RemoteViewsView? RemoteViews View
Android*/
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;//创建挂起意图对象,用于启动主活动
PendingIntent pendingIntent = null;
if (privacyMode) {
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));//设置小部件文本为隐私模式下的提示文本
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);//用于动态更新
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);//更新小部件布局
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}

@ -24,10 +24,10 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_2x extends NoteWidgetProvider {//小部件2x布局类
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);//调用父类更新方法
super.update(context, appWidgetManager, appWidgetIds);
}
@Override

@ -24,14 +24,14 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_4x extends NoteWidgetProvider {//小部件4x布局类
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);//调用父类更新方法
super.update(context, appWidgetManager, appWidgetIds);
}
protected int getLayoutId() {
return R.layout.widget_4x;//返回4x布局资源ID
return R.layout.widget_4x;
}
@Override

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#88555555" />
<item android:state_selected="true" android:color="#ff999999" />
<item android:color="#ff000000" />
</selector>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save