Compare commits

..

1 Commits

Author SHA1 Message Date
玛卡巴卡 b20ee1c295 1
1 week ago

@ -93,6 +93,14 @@ public class Notes {
* Intentcall_date
*/
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
/**
* IntentIDtag_id
*/
public static final String INTENT_EXTRA_TAG_ID = "net.micode.notes.tag_id";
/**
* Intenttag_name
*/
public static final String INTENT_EXTRA_TAG_NAME = "net.micode.notes.tag_name";
/**
* WidgetWidget
@ -125,11 +133,45 @@ public class Notes {
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* ContentProviderURIURI
* Content URI
* content://micode_notes/data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
/**
* Content URI
* content://micode_notes/tag
*/
public static final Uri CONTENT_TAG_URI = Uri.parse("content://" + AUTHORITY + "/tag");
/**
* 便-Content URI
* content://micode_notes/note_tag_relation
*/
public static final Uri CONTENT_NOTE_TAG_RELATION_URI = Uri.parse("content://" + AUTHORITY + "/note_tag_relation");
/**
* Content URI
* content://micode_notes/encryption
*/
public static final Uri CONTENT_ENCRYPTION_URI = Uri.parse("content://" + AUTHORITY + "/encryption");
/**
* Content URI
* content://micode_notes/security_question
*/
public static final Uri CONTENT_SECURITY_QUESTION_URI = Uri.parse("content://" + AUTHORITY + "/security_question");
/**
* Intent便is_encrypted
*/
public static final String INTENT_EXTRA_IS_ENCRYPTED = "net.micode.notes.is_encrypted";
/**
* Intent便password
*/
public static final String INTENT_EXTRA_PASSWORD = "net.micode.notes.password";
/**
* /
* noteContentProvider
@ -246,6 +288,162 @@ public class Notes {
public static final String VERSION = "version";
}
/**
*
* tag
*/
public interface TagColumns {
/**
* ID
* <P> : INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
*
* <P> : TEXT </P>
*/
public static final String NAME = "name";
/**
*
* <P> : INTEGER (int) </P>
*/
public static final String COLOR = "color";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
}
/**
* 便-
* note_tag_relation
*/
public interface NoteTagRelationColumns {
/**
* ID
* <P> : INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* 便ID
* <P> : INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
* ID
* <P> : INTEGER (long) </P>
*/
public static final String TAG_ID = "tag_id";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
}
/**
*
* note_encryption
*/
public interface EncryptionColumns {
/**
* ID
* <P> : INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* 便ID
* <P> : INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
*
* <P> : TEXT </P>
*/
public static final String PASSWORD_HASH = "password_hash";
/**
*
* <P> : TEXT </P>
*/
public static final String SALT = "salt";
/**
*
* <P> : TEXT </P>
*/
public static final String IV = "iv";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
}
/**
*
* security_question
*/
public interface SecurityQuestionColumns {
/**
* ID
* <P> : INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* 便ID
* <P> : INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
*
* <P> : TEXT </P>
*/
public static final String QUESTION = "question";
/**
*
* <P> : TEXT </P>
*/
public static final String ANSWER_HASH = "answer_hash";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> : INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
}
/**
*
* datadata

@ -45,18 +45,26 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db";
/**
* 6
* 7
*/
private static final int DB_VERSION = 6;
private static final int DB_VERSION = 7;
/**
* notedata便-
* notedata便-
*/
public interface TABLE {
// 笔记/文件夹表名称
public static final String NOTE = "note";
// 笔记明细数据表名称(存储文本、通话记录等具体内容)
public static final String DATA = "data";
// 标签表名称
public static final String TAG = "tag";
// 便签-标签关联表名称
public static final String NOTE_TAG_RELATION = "note_tag_relation";
// 加密信息表名称
public static final String ENCRYPTION = "note_encryption";
// 密保问题表名称
public static final String SECURITY_QUESTION = "security_question";
}
/**
@ -127,6 +135,88 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* tagSQL
* tag
*/
private static final String CREATE_TAG_TABLE_SQL =
"CREATE TABLE " + TABLE.TAG + "(" +
"_id INTEGER PRIMARY KEY," + // 标签ID
"name TEXT NOT NULL UNIQUE," + // 标签名称(唯一)
"color INTEGER NOT NULL DEFAULT 0," + // 标签颜色
"created_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
"modified_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)" + // 修改时间
");";
/**
* note_tag_relationSQL
* note_tag_relation便
*/
private static final String CREATE_NOTE_TAG_RELATION_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE_TAG_RELATION + "(" +
"_id INTEGER PRIMARY KEY," + // 关联ID
"note_id INTEGER NOT NULL," + // 便签ID
"tag_id INTEGER NOT NULL," + // 标签ID
"created_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
"UNIQUE(note_id, tag_id)" + // 确保一个便签不会重复关联同一个标签
");";
/**
* note_tag_relationSQL
*
*/
private static final String CREATE_NOTE_TAG_RELATION_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_tag_index ON " +
TABLE.NOTE_TAG_RELATION + "(note_id, tag_id);";
/**
* SQL
* note_encryption便
*/
private static final String CREATE_ENCRYPTION_TABLE_SQL =
"CREATE TABLE " + TABLE.ENCRYPTION + "(" +
"_id INTEGER PRIMARY KEY," + // 加密信息ID
"note_id INTEGER NOT NULL UNIQUE," + // 便签ID唯一
"password_hash TEXT NOT NULL," + // 加密后的密码哈希值
"salt TEXT NOT NULL," + // 加密密钥的盐值
"iv TEXT NOT NULL," + // 初始化向量
"created_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
"modified_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间
"FOREIGN KEY(note_id) REFERENCES " + TABLE.NOTE + "(_id) ON DELETE CASCADE" + // 外键约束,便签删除时级联删除加密信息
");";
/**
* SQL
* security_question便
*/
private static final String CREATE_SECURITY_QUESTION_TABLE_SQL =
"CREATE TABLE " + TABLE.SECURITY_QUESTION + "(" +
"_id INTEGER PRIMARY KEY," + // 密保问题ID
"note_id INTEGER NOT NULL," + // 便签ID
"question TEXT NOT NULL," + // 密保问题
"answer_hash TEXT NOT NULL," + // 密保答案的哈希值
"created_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间
"modified_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间
"FOREIGN KEY(note_id) REFERENCES " + TABLE.NOTE + "(_id) ON DELETE CASCADE," + // 外键约束,便签删除时级联删除密保问题
"UNIQUE(note_id)" + // 确保一个便签只有一个密保问题
");";
/**
* SQL
*
*/
private static final String CREATE_ENCRYPTION_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS encryption_note_id_index ON " +
TABLE.ENCRYPTION + "(note_id);";
/**
* SQL
*
*/
private static final String CREATE_SECURITY_QUESTION_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS security_question_note_id_index ON " +
TABLE.SECURITY_QUESTION + "(note_id);";
// ====================== 数据库触发器SQL语句note表 ======================
@ -394,7 +484,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
*
* notedata
* notedata便-
*
* @param db SQLiteDatabase
*/
@ -402,6 +492,93 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
createTagTable(db);
createNoteTagRelationTable(db);
createEncryptionTable(db);
createSecurityQuestionTable(db);
}
/**
*
* 使
*
* @param db SQLiteDatabase
*/
public void ensureTablesExist(SQLiteDatabase db) {
try {
// 尝试查询tag表如果不存在会抛出异常
db.rawQuery("SELECT * FROM " + TABLE.TAG + " LIMIT 1", null).close();
} catch (Exception e) {
// tag表不存在创建它
createTagTable(db);
}
try {
// 尝试查询note_tag_relation表如果不存在会抛出异常
db.rawQuery("SELECT * FROM " + TABLE.NOTE_TAG_RELATION + " LIMIT 1", null).close();
} catch (Exception e) {
// note_tag_relation表不存在创建它
createNoteTagRelationTable(db);
}
try {
// 尝试查询encryption表如果不存在会抛出异常
db.rawQuery("SELECT * FROM " + TABLE.ENCRYPTION + " LIMIT 1", null).close();
} catch (Exception e) {
// encryption表不存在创建它
createEncryptionTable(db);
}
try {
// 尝试查询security_question表如果不存在会抛出异常
db.rawQuery("SELECT * FROM " + TABLE.SECURITY_QUESTION + " LIMIT 1", null).close();
} catch (Exception e) {
// security_question表不存在创建它
createSecurityQuestionTable(db);
}
}
/**
* tag
*
* @param db SQLiteDatabase
*/
public void createTagTable(SQLiteDatabase db) {
db.execSQL(CREATE_TAG_TABLE_SQL);
Log.d(TAG, "tag table has been created");
}
/**
* note_tag_relation
*
* @param db SQLiteDatabase
*/
public void createNoteTagRelationTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TAG_RELATION_TABLE_SQL);
db.execSQL(CREATE_NOTE_TAG_RELATION_INDEX_SQL);
Log.d(TAG, "note_tag_relation table has been created");
}
/**
*
*
* @param db SQLiteDatabase
*/
public void createEncryptionTable(SQLiteDatabase db) {
db.execSQL(CREATE_ENCRYPTION_TABLE_SQL);
db.execSQL(CREATE_ENCRYPTION_INDEX_SQL);
Log.d(TAG, "encryption table has been created");
}
/**
*
*
* @param db SQLiteDatabase
*/
public void createSecurityQuestionTable(SQLiteDatabase db) {
db.execSQL(CREATE_SECURITY_QUESTION_TABLE_SQL);
db.execSQL(CREATE_SECURITY_QUESTION_INDEX_SQL);
Log.d(TAG, "security_question table has been created");
}
/**
@ -447,7 +624,28 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
// 从版本5升级到版本6
if (oldVersion == 5) {
// 创建tag表
db.execSQL(CREATE_TAG_TABLE_SQL);
// 创建note_tag_relation表
db.execSQL(CREATE_NOTE_TAG_RELATION_TABLE_SQL);
// 创建索引
db.execSQL(CREATE_NOTE_TAG_RELATION_INDEX_SQL);
oldVersion++;
}
// 从版本6升级到版本7
if (oldVersion == 6) {
// 创建加密信息表
db.execSQL(CREATE_ENCRYPTION_TABLE_SQL);
// 创建密保问题表
db.execSQL(CREATE_SECURITY_QUESTION_TABLE_SQL);
// 创建索引
db.execSQL(CREATE_ENCRYPTION_INDEX_SQL);
db.execSQL(CREATE_SECURITY_QUESTION_INDEX_SQL);
oldVersion++;
}
// 如果需要,重建触发器
if (reCreateTriggers) {

@ -91,6 +91,38 @@ public class NotesProvider extends ContentProvider {
* UriSearchManager
*/
private static final int URI_SEARCH_SUGGEST = 6;
/**
* Uri/tag
*/
private static final int URI_TAG = 7;
/**
* Uri/tagIDtag/1
*/
private static final int URI_TAG_ITEM = 8;
/**
* Uri/note_tag_relation
*/
private static final int URI_NOTE_TAG_RELATION = 9;
/**
* Uri/note_tag_relationIDnote_tag_relation/1
*/
private static final int URI_NOTE_TAG_RELATION_ITEM = 10;
/**
* Uri/
*/
private static final int URI_ENCRYPTION = 11;
/**
* Uri/IDencryption/1
*/
private static final int URI_ENCRYPTION_ITEM = 12;
/**
* Uri/
*/
private static final int URI_SECURITY_QUESTION = 13;
/**
* Uri/IDsecurity_question/1
*/
private static final int URI_SECURITY_QUESTION_ITEM = 14;
/**
@ -114,6 +146,24 @@ public class NotesProvider extends ContentProvider {
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
// 匹配搜索建议带关键词content://micode_notes/suggestions/query/关键词 -> URI_SEARCH_SUGGEST
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
// 匹配tag表所有数据content://micode_notes/tag -> URI_TAG
mMatcher.addURI(Notes.AUTHORITY, "tag", URI_TAG);
// 匹配tag表单条数据content://micode_notes/tag/# -> URI_TAG_ITEM
mMatcher.addURI(Notes.AUTHORITY, "tag/#", URI_TAG_ITEM);
// 匹配note_tag_relation表所有数据content://micode_notes/note_tag_relation -> URI_NOTE_TAG_RELATION
mMatcher.addURI(Notes.AUTHORITY, "note_tag_relation", URI_NOTE_TAG_RELATION);
// 匹配note_tag_relation表单条数据content://micode_notes/note_tag_relation/# -> URI_NOTE_TAG_RELATION_ITEM
mMatcher.addURI(Notes.AUTHORITY, "note_tag_relation/#", URI_NOTE_TAG_RELATION_ITEM);
// 匹配加密信息表所有数据content://micode_notes/encryption -> URI_ENCRYPTION
mMatcher.addURI(Notes.AUTHORITY, "encryption", URI_ENCRYPTION);
// 匹配加密信息表单条数据content://micode_notes/encryption/# -> URI_ENCRYPTION_ITEM
mMatcher.addURI(Notes.AUTHORITY, "encryption/#", URI_ENCRYPTION_ITEM);
// 匹配密保问题表所有数据content://micode_notes/security_question -> URI_SECURITY_QUESTION
mMatcher.addURI(Notes.AUTHORITY, "security_question", URI_SECURITY_QUESTION);
// 匹配密保问题表单条数据content://micode_notes/security_question/# -> URI_SECURITY_QUESTION_ITEM
mMatcher.addURI(Notes.AUTHORITY, "security_question/#", URI_SECURITY_QUESTION_ITEM);
}
@ -152,6 +202,15 @@ public class NotesProvider extends ContentProvider {
public boolean onCreate() {
// 获取NotesDatabaseHelper的单例实例上下文使用ContentProvider的上下文
mHelper = NotesDatabaseHelper.getInstance(getContext());
// 确保标签相关的表结构存在
try {
SQLiteDatabase db = mHelper.getWritableDatabase();
mHelper.ensureTablesExist(db);
} catch (Exception e) {
// 忽略异常,继续运行
}
return true;
}
@ -295,6 +354,54 @@ public class NotesProvider extends ContentProvider {
}
break;
case URI_TAG:
// 查询tag表的所有数据
c = db.query(TABLE.TAG, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_TAG_ITEM:
// 获取Uri中的ID
id = uri.getPathSegments().get(1);
// 查询tag表的单条数据
c = db.query(TABLE.TAG, projection, "_id=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_NOTE_TAG_RELATION:
// 查询note_tag_relation表的所有数据
c = db.query(TABLE.NOTE_TAG_RELATION, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_NOTE_TAG_RELATION_ITEM:
// 获取Uri中的ID
id = uri.getPathSegments().get(1);
// 查询note_tag_relation表的单条数据
c = db.query(TABLE.NOTE_TAG_RELATION, projection, "_id=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_ENCRYPTION:
// 查询加密信息表的所有数据
c = db.query(TABLE.ENCRYPTION, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_ENCRYPTION_ITEM:
// 获取Uri中的ID
id = uri.getPathSegments().get(1);
// 查询加密信息表的单条数据
c = db.query(TABLE.ENCRYPTION, projection, "_id=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SECURITY_QUESTION:
// 查询密保问题表的所有数据
c = db.query(TABLE.SECURITY_QUESTION, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_SECURITY_QUESTION_ITEM:
// 获取Uri中的ID
id = uri.getPathSegments().get(1);
// 查询密保问题表的单条数据
c = db.query(TABLE.SECURITY_QUESTION, projection, "_id=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
default:
// 未知Uri抛出异常
throw new IllegalArgumentException("Unknown URI " + uri);
@ -318,7 +425,7 @@ public class NotesProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
// 获取可写的SQLiteDatabase对象插入操作需要写权限
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0; // 存储插入的ID
long dataId = 0, noteId = 0, insertedId = 0, tagId = 0, relationId = 0; // 存储插入的ID
// 根据Uri匹配的类型执行插入逻辑
switch (mMatcher.match(uri)) {
@ -337,6 +444,39 @@ public class NotesProvider extends ContentProvider {
// 插入data表获取插入的ID
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
case URI_TAG:
// 插入tag表获取插入的ID
insertedId = tagId = db.insert(TABLE.TAG, null, values);
break;
case URI_NOTE_TAG_RELATION:
// 插入note_tag_relation表时获取关联的noteId和tagId
if (values.containsKey(Notes.NoteTagRelationColumns.NOTE_ID)) {
noteId = values.getAsLong(Notes.NoteTagRelationColumns.NOTE_ID);
}
if (values.containsKey(Notes.NoteTagRelationColumns.TAG_ID)) {
tagId = values.getAsLong(Notes.NoteTagRelationColumns.TAG_ID);
}
// 插入note_tag_relation表获取插入的ID
insertedId = relationId = db.insert(TABLE.NOTE_TAG_RELATION, null, values);
break;
case URI_ENCRYPTION:
// 插入加密信息表时获取关联的noteId
if (values.containsKey(Notes.EncryptionColumns.NOTE_ID)) {
noteId = values.getAsLong(Notes.EncryptionColumns.NOTE_ID);
}
// 插入加密信息表获取插入的ID
insertedId = db.insert(TABLE.ENCRYPTION, null, values);
break;
case URI_SECURITY_QUESTION:
// 插入密保问题表时获取关联的noteId
if (values.containsKey(Notes.SecurityQuestionColumns.NOTE_ID)) {
noteId = values.getAsLong(Notes.SecurityQuestionColumns.NOTE_ID);
}
// 插入密保问题表获取插入的ID
insertedId = db.insert(TABLE.SECURITY_QUESTION, null, values);
break;
default:
// 未知Uri抛出异常
@ -355,7 +495,17 @@ public class NotesProvider extends ContentProvider {
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 发送通知tag表数据变更通知对应的Uri
if (tagId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_TAG_URI, tagId), null);
}
// 发送通知note_tag_relation表数据变更通知对应的Uri
if (relationId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_TAG_RELATION_URI, relationId), null);
}
// 返回包含插入ID的新Uri
return ContentUris.withAppendedId(uri, insertedId);
@ -377,6 +527,7 @@ public class NotesProvider extends ContentProvider {
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false; // 标记是否删除的是data表数据
long noteId = 0; // 用于存储便签ID以便发送通知
long tagId = 0; // 用于存储标签ID以便发送通知
// 根据Uri匹配的类型执行删除逻辑
switch (mMatcher.match(uri)) {
@ -410,8 +561,47 @@ public class NotesProvider extends ContentProvider {
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
case URI_TAG:
// 删除tag表数据
count = db.delete(TABLE.TAG, selection, selectionArgs);
break;
case URI_TAG_ITEM:
// 获取Uri中的ID删除tag表单条数据
id = uri.getPathSegments().get(1);
tagId = Long.valueOf(id);
count = db.delete(TABLE.TAG, "_id=" + id + parseSelection(selection), selectionArgs);
break;
case URI_NOTE_TAG_RELATION:
// 删除note_tag_relation表数据
count = db.delete(TABLE.NOTE_TAG_RELATION, selection, selectionArgs);
break;
case URI_NOTE_TAG_RELATION_ITEM:
// 获取Uri中的ID删除note_tag_relation表单条数据
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.NOTE_TAG_RELATION, "_id=" + id + parseSelection(selection), selectionArgs);
break;
case URI_ENCRYPTION:
// 删除加密信息表数据
count = db.delete(TABLE.ENCRYPTION, selection, selectionArgs);
break;
case URI_ENCRYPTION_ITEM:
// 获取Uri中的ID删除加密信息表单条数据
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.ENCRYPTION, "_id=" + id + parseSelection(selection), selectionArgs);
break;
case URI_SECURITY_QUESTION:
// 删除密保问题表数据
count = db.delete(TABLE.SECURITY_QUESTION, selection, selectionArgs);
break;
case URI_SECURITY_QUESTION_ITEM:
// 获取Uri中的ID删除密保问题表单条数据
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.SECURITY_QUESTION, "_id=" + id + parseSelection(selection), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
@ -428,6 +618,11 @@ public class NotesProvider extends ContentProvider {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 如果是标签相关操作通知对应的标签Uri
if (tagId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_TAG_URI, tagId), null);
}
// 通知当前Uri的数据变更
getContext().getContentResolver().notifyChange(uri, null);
}
@ -451,6 +646,7 @@ public class NotesProvider extends ContentProvider {
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false; // 标记是否更新的是data表数据
long noteId = 0; // 用于存储便签ID以便发送通知
long tagId = 0; // 用于存储标签ID以便发送通知
// 根据Uri匹配的类型执行更新逻辑
switch (mMatcher.match(uri)) {
@ -479,8 +675,47 @@ public class NotesProvider extends ContentProvider {
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
case URI_TAG:
// 更新tag表数据
count = db.update(TABLE.TAG, values, selection, selectionArgs);
break;
case URI_TAG_ITEM:
// 获取Uri中的ID更新tag表单条数据
id = uri.getPathSegments().get(1);
tagId = Long.valueOf(id);
count = db.update(TABLE.TAG, values, "_id=" + id + parseSelection(selection), selectionArgs);
break;
case URI_NOTE_TAG_RELATION:
// 更新note_tag_relation表数据
count = db.update(TABLE.NOTE_TAG_RELATION, values, selection, selectionArgs);
break;
case URI_NOTE_TAG_RELATION_ITEM:
// 获取Uri中的ID更新note_tag_relation表单条数据
id = uri.getPathSegments().get(1);
count = db.update(TABLE.NOTE_TAG_RELATION, values, "_id=" + id + parseSelection(selection), selectionArgs);
break;
case URI_ENCRYPTION:
// 更新加密信息表数据
count = db.update(TABLE.ENCRYPTION, values, selection, selectionArgs);
break;
case URI_ENCRYPTION_ITEM:
// 获取Uri中的ID更新加密信息表单条数据
id = uri.getPathSegments().get(1);
count = db.update(TABLE.ENCRYPTION, values, "_id=" + id + parseSelection(selection), selectionArgs);
break;
case URI_SECURITY_QUESTION:
// 更新密保问题表数据
count = db.update(TABLE.SECURITY_QUESTION, values, selection, selectionArgs);
break;
case URI_SECURITY_QUESTION_ITEM:
// 获取Uri中的ID更新密保问题表单条数据
id = uri.getPathSegments().get(1);
count = db.update(TABLE.SECURITY_QUESTION, values, "_id=" + id + parseSelection(selection), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
@ -497,6 +732,11 @@ public class NotesProvider extends ContentProvider {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 如果是标签相关操作通知对应的标签Uri
if (tagId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_TAG_URI, tagId), null);
}
// 通知当前Uri的数据变更
getContext().getContentResolver().notifyChange(uri, null);
}

@ -0,0 +1,249 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.util.Base64;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* AES/
*/
public class EncryptionUtils {
private static final String TAG = "EncryptionUtils";
// ====================== 常量定义 ======================
/**
* AES/CBC/PKCS7Padding
*/
public static final String ENCRYPTION_ALGORITHM = "AES/CBC/PKCS7Padding";
/**
* PBKDF2WithHmacSHA256
*/
private static final String KEY_GENERATION_ALGORITHM = "PBKDF2WithHmacSHA256";
/**
* SHA-256
*/
private static final String HASH_ALGORITHM = "SHA-256";
/**
* 256
*/
private static final int KEY_LENGTH = 256;
/**
* 16
*/
private static final int SALT_LENGTH = 16;
/**
* 16
*/
private static final int IV_LENGTH = 16;
/**
* 10000
*/
private static final int ITERATION_COUNT = 10000;
// ====================== 工具方法 ======================
/**
*
* @return Base64
*/
public static String generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH];
random.nextBytes(salt);
return Base64.encodeToString(salt, Base64.NO_WRAP);
}
/**
*
* @return Base64
*/
public static String generateIv() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return Base64.encodeToString(iv, Base64.NO_WRAP);
}
/**
*
* @param password
* @param salt Base64
* @return
* @throws GeneralSecurityException
*/
public static SecretKey generateKey(String password, String salt) throws GeneralSecurityException {
byte[] saltBytes = Base64.decode(salt, Base64.NO_WRAP);
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, ITERATION_COUNT, KEY_LENGTH);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_GENERATION_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
}
/**
*
* @param plainText
* @param password
* @param salt Base64
* @param iv Base64
* @return Base64
* @throws GeneralSecurityException
*/
public static String encrypt(String plainText, String password, String salt, String iv) throws GeneralSecurityException {
SecretKey key = generateKey(password, salt);
byte[] ivBytes = Base64.decode(iv, Base64.NO_WRAP);
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
try {
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
return Base64.encodeToString(encryptedBytes, Base64.NO_WRAP);
} catch (UnsupportedEncodingException e) {
// UTF-8 是标准字符编码,不应该抛出此异常
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
/**
*
* @param encryptedText Base64
* @param password
* @param salt Base64
* @param iv Base64
* @return
* @throws GeneralSecurityException
*/
public static String decrypt(String encryptedText, String password, String salt, String iv) throws GeneralSecurityException {
SecretKey key = generateKey(password, salt);
byte[] ivBytes = Base64.decode(iv, Base64.NO_WRAP);
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
byte[] encryptedBytes = Base64.decode(encryptedText, Base64.NO_WRAP);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
try {
return new String(decryptedBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
// UTF-8 是标准字符编码,不应该抛出此异常
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
/**
*
* @param password
* @param salt Base64
* @return Base64
*/
public static String generatePasswordHash(String password, String salt) {
try {
byte[] saltBytes = Base64.decode(salt, Base64.NO_WRAP);
byte[] passwordBytes = password.getBytes("UTF-8");
// 合并密码和盐值
byte[] combinedBytes = new byte[passwordBytes.length + saltBytes.length];
System.arraycopy(passwordBytes, 0, combinedBytes, 0, passwordBytes.length);
System.arraycopy(saltBytes, 0, combinedBytes, passwordBytes.length, saltBytes.length);
// 计算哈希值
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
byte[] hashBytes = digest.digest(combinedBytes);
return Base64.encodeToString(hashBytes, Base64.NO_WRAP);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "Error generating password hash", e);
return null;
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error generating password hash", e);
return null;
}
}
/**
*
* @param password
* @param salt Base64
* @param storedHash Base64
* @return
*/
public static boolean verifyPassword(String password, String salt, String storedHash) {
String generatedHash = generatePasswordHash(password, salt);
return generatedHash != null && generatedHash.equals(storedHash);
}
/**
*
* @param answer
* @param salt Base64
* @return Base64
*/
public static String generateAnswerHash(String answer, String salt) {
// 转换为小写并去除空格,提高用户体验
String normalizedAnswer = answer.toLowerCase().trim();
return generatePasswordHash(normalizedAnswer, salt);
}
/**
*
* @param answer
* @param salt Base64
* @param storedHash Base64
* @return
*/
public static boolean verifyAnswer(String answer, String salt, String storedHash) {
String normalizedAnswer = answer.toLowerCase().trim();
String generatedHash = generatePasswordHash(normalizedAnswer, salt);
return generatedHash != null && generatedHash.equals(storedHash);
}
/**
*
* @return Base64
* @throws NoSuchAlgorithmException
*/
public static String generateRandomKey() throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(KEY_LENGTH);
SecretKey key = keyGenerator.generateKey();
return Base64.encodeToString(key.getEncoded(), Base64.NO_WRAP);
}
}

@ -217,6 +217,18 @@ public class ResourceParser {
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
/**
* ID
* 使
* @param positionType 0=, 1=, 2=, 3=
* @return ID
*/
public static int getNightBgRes(int positionType) {
// 夜间模式下使用简单的纯色背景
// 实际应用中可以创建专门的夜间模式背景图片
return android.R.color.transparent; // 透明背景,使用主题背景色
}
}
// ======================= 小部件背景资源类 =======================

@ -28,8 +28,13 @@ import android.text.TextUtils; // 文本工具
import net.micode.notes.data.Contact; // 联系人工具类
import net.micode.notes.data.Notes; // Notes主类
import net.micode.notes.data.Notes.NoteColumns; // 便签表列定义
import net.micode.notes.data.Notes.TagColumns; // 标签表列定义
import net.micode.notes.data.Notes.NoteTagRelationColumns; // 便签-标签关联表列定义
import net.micode.notes.tool.DataUtils; // 数据工具
// Java集合
import java.util.ArrayList;
// ======================= 便签项数据模型 =======================
/**
* NoteItemData - 便
@ -105,6 +110,12 @@ public class NoteItemData {
private boolean mIsOnlyOneItem; // 是否是唯一一项
private boolean mIsOneNoteFollowingFolder; // 是否是文件夹后的唯一便签
private boolean mIsMultiNotesFollowingFolder; // 是否是文件夹后的多个便签之一
// 标签相关
private ArrayList<String> mTags; // 便签的标签列表
// 上下文
private Context mContext; // 上下文,用于数据库查询
// ======================= 构造函数 =======================
@ -117,10 +128,11 @@ public class NoteItemData {
* 3.
* 4.
*
* @param context
* @param context
* @param cursor PROJECTION
*/
public NoteItemData(Context context, Cursor cursor) {
mContext = context;
// 1. 读取基本字段
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
@ -161,6 +173,9 @@ public class NoteItemData {
mName = "";
}
// 5. 加载标签数据
loadTags(context);
// 4. 检查位置状态
checkPostion(cursor);
}
@ -406,6 +421,107 @@ public class NoteItemData {
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
/**
* 便
* @param context
*/
private void loadTags(Context context) {
mTags = new ArrayList<>();
try {
// 查询便签关联的标签
Cursor tagCursor = context.getContentResolver().query(
Notes.CONTENT_NOTE_TAG_RELATION_URI,
new String[]{Notes.NoteTagRelationColumns.TAG_ID},
Notes.NoteTagRelationColumns.NOTE_ID + "=?",
new String[]{String.valueOf(mId)},
null);
if (tagCursor != null) {
try {
while (tagCursor.moveToNext()) {
long tagId = tagCursor.getLong(0);
// 查询标签详情
Cursor tagDetailCursor = context.getContentResolver().query(
Notes.CONTENT_TAG_URI,
new String[]{TagColumns.NAME},
TagColumns.ID + "=?",
new String[]{String.valueOf(tagId)},
null);
if (tagDetailCursor != null) {
try {
if (tagDetailCursor.moveToFirst()) {
String tagName = tagDetailCursor.getString(0);
mTags.add(tagName);
}
} finally {
tagDetailCursor.close();
}
}
}
} finally {
tagCursor.close();
}
}
} catch (Exception e) {
// 捕获数据库异常,如表不存在等情况
// 此时标签功能可能尚未初始化,优雅处理
mTags.clear();
}
}
/**
* 便
* @return
*/
public ArrayList<String> getTags() {
return mTags;
}
/**
* 便
* @return
*/
public boolean hasTags() {
return mTags != null && !mTags.isEmpty();
}
/**
*
* @return
*/
private Context getContext() {
return mContext;
}
/**
* 便
* @return
*/
public boolean isEncrypted() {
try {
// 查询加密信息
Cursor cursor = getContext().getContentResolver().query(
Notes.CONTENT_ENCRYPTION_URI,
null,
Notes.EncryptionColumns.NOTE_ID + "=?",
new String[]{String.valueOf(mId)},
null
);
boolean isEncrypted = cursor != null && cursor.moveToFirst();
if (cursor != null) {
cursor.close();
}
return isEncrypted;
} catch (Exception e) {
// 表不存在或其他异常返回false
return false;
}
}
// ======================= 静态工具方法 =======================

@ -95,6 +95,7 @@ import java.io.IOException; // IO异常
import java.io.InputStream; // 输入流
import java.io.InputStreamReader; // 输入流读取器
// Java集合
import java.util.ArrayList; // 数组列表
import java.util.HashSet; // 哈希集合
import java.util.List; // 列表接口
import java.util.Map; // 映射接口
@ -195,8 +196,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private NoteItemData mFocusNoteDataItem;
/** 当前选中的标签ID - 用于按标签筛选便签,-1表示不筛选 */
private long mCurrentTagId = -1;
/** 夜间模式状态 */
private static final String PREF_NIGHT_MODE = "night_mode";
private boolean mIsNightMode = false;
// ======================= 数据库查询条件常量 =======================
/** 普通文件夹查询条件 - 查询指定父文件夹下的便签 */
@ -225,13 +230,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载夜间模式设置
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mIsNightMode = prefs.getBoolean(PREF_NIGHT_MODE, false);
applyNightMode();
// 设置布局
setContentView(R.layout.note_list);
// 初始化资源
initResources();
/**
* 使便
* 使便
*/
setAppInfoFromRawRes();
}
@ -344,6 +355,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mState = ListEditState.NOTE_LIST; // 初始状态为便签列表
mModeCallBack = new ModeCallback(); // 创建多选模式回调
// 设置列表背景色,根据当前夜间模式状态
if (mIsNightMode) {
mNotesListView.setBackgroundColor(getResources().getColor(R.color.night_list_background));
} else {
mNotesListView.setBackgroundColor(getResources().getColor(android.R.color.white));
}
// 更新标题栏
updateTitleBar();
}
@ -583,13 +601,99 @@ 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 = { String.valueOf(mCurrentFolderId) };
// 基础查询条件
StringBuilder selection = new StringBuilder();
ArrayList<String> selectionArgs = new ArrayList<>();
// 根据当前文件夹添加查询条件
if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) {
selection.append(ROOT_FOLDER_SELECTION);
} else {
selection.append(NORMAL_SELECTION);
}
selectionArgs.add(String.valueOf(mCurrentFolderId));
// 如果有标签筛选,添加标签关联条件
if (mCurrentTagId != -1) {
Log.d("NotesListActivity", "开始标签筛选标签ID" + mCurrentTagId);
Log.d("NotesListActivity", "基础查询条件:" + selection.toString());
try {
// 查询带有该标签的便签ID
Log.d("NotesListActivity", "开始查询标签关联的便签ID");
Cursor relationCursor = getContentResolver().query(
Notes.CONTENT_NOTE_TAG_RELATION_URI,
new String[]{Notes.NoteTagRelationColumns.NOTE_ID},
Notes.NoteTagRelationColumns.TAG_ID + "=?",
new String[]{String.valueOf(mCurrentTagId)},
null);
if (relationCursor != null) {
int count = relationCursor.getCount();
Log.d("NotesListActivity", "标签 " + mCurrentTagId + " 关联了 " + count + " 个便签");
if (count > 0) {
// 构建便签ID列表
StringBuilder noteIdList = new StringBuilder();
boolean first = true;
while (relationCursor.moveToNext()) {
long noteId = relationCursor.getLong(0);
if (first) {
first = false;
} else {
noteIdList.append(",");
}
noteIdList.append(noteId);
Log.d("NotesListActivity", "便签 " + noteId + " 关联了标签 " + mCurrentTagId);
}
relationCursor.close();
// 添加便签ID筛选条件
selection.append(" AND ").append(NoteColumns.ID).append(" IN (").append(noteIdList).append(")");
Log.d("NotesListActivity", "最终查询条件:" + selection.toString());
} else {
// 没有关联的便签添加一个永远为false的条件
selection.append(" AND 1=0");
Log.d("NotesListActivity", "标签 " + mCurrentTagId + " 没有关联的便签,添加 1=0 条件");
relationCursor.close();
}
} else {
Log.d("NotesListActivity", "relationCursor 为 null");
}
} catch (Exception e) {
Log.d("NotesListActivity", "标签筛选异常:" + e.getMessage());
e.printStackTrace();
// 表不存在或其他异常,忽略标签筛选
}
} else {
Log.d("NotesListActivity", "mCurrentTagId 为 -1不进行标签筛选");
}
// 生成排序字符串,确保置顶便签在最前面
StringBuilder sortOrder = new StringBuilder();
sortOrder.append(NoteColumns.PINNED).append(" DESC,");
sortOrder.append(NoteColumns.TYPE).append(" DESC,");
// 根据当前排序模式添加相应的排序字段
switch (mCurrentSortMode) {
case SORT_BY_NAME:
sortOrder.append(NoteColumns.SNIPPET).append(" ASC");
break;
case SORT_BY_MODIFIED_DATE:
sortOrder.append(NoteColumns.MODIFIED_DATE).append(" DESC");
break;
case SORT_BY_CREATED_DATE:
sortOrder.append(NoteColumns.CREATED_DATE).append(" DESC");
break;
default:
sortOrder.append(NoteColumns.MODIFIED_DATE).append(" DESC");
break;
}
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs,
NoteColumns.PINNED + " DESC," + NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection.toString(),
selectionArgs.toArray(new String[0]), sortOrder.toString());
}
/**
@ -1158,6 +1262,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
/**
*
*/
// 排序方式常量
private static final int SORT_BY_NAME = 0;
private static final int SORT_BY_MODIFIED_DATE = 1;
private static final int SORT_BY_CREATED_DATE = 2;
private int mCurrentSortMode = SORT_BY_MODIFIED_DATE; // 默认按修改时间排序
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
@ -1181,10 +1291,135 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
createNewNote();
} else if (itemId == R.id.menu_search) {
onSearchRequested();
} else if (itemId == R.id.menu_sort) {
showSortOptions();
} else if (itemId == R.id.menu_tags) {
showTagFilterDialog();
} else if (itemId == R.id.menu_night_mode) {
toggleNightMode();
}
return true;
}
/**
*
*/
private void showSortOptions() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Sort By");
final String[] options = {"Note Name", "Modified Date", "Created Date"};
builder.setSingleChoiceItems(options, mCurrentSortMode, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mCurrentSortMode = which;
startAsyncNotesListQuery();
dialog.dismiss();
}
});
builder.setNegativeButton("Cancel", null);
builder.show();
}
/**
*
*/
private void showTagFilterDialog() {
// 准备标签列表
final List<String> tagNames = new ArrayList<>();
final List<Long> tagIds = new ArrayList<>();
// 添加"全部"选项
tagNames.add("全部");
tagIds.add(-1L);
try {
// 查询所有标签
Cursor tagCursor = getContentResolver().query(
Notes.CONTENT_TAG_URI,
new String[]{Notes.TagColumns.ID, Notes.TagColumns.NAME},
null, null, Notes.TagColumns.NAME + " ASC");
if (tagCursor != null) {
try {
while (tagCursor.moveToNext()) {
tagNames.add(tagCursor.getString(tagCursor.getColumnIndex(Notes.TagColumns.NAME)));
tagIds.add(tagCursor.getLong(tagCursor.getColumnIndex(Notes.TagColumns.ID)));
}
} finally {
tagCursor.close();
}
}
} catch (Exception e) {
// 表不存在或其他异常,忽略,继续显示对话框
}
// 显示标签选择对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("按标签筛选");
// 查找当前选中的标签索引
int checkedItem = 0;
for (int i = 0; i < tagIds.size(); i++) {
if (tagIds.get(i) == mCurrentTagId) {
checkedItem = i;
break;
}
}
builder.setSingleChoiceItems(tagNames.toArray(new String[0]), checkedItem, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mCurrentTagId = tagIds.get(which);
startAsyncNotesListQuery();
dialog.dismiss();
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
// ======================= 夜间模式 =======================
/**
*
*/
private void toggleNightMode() {
mIsNightMode = !mIsNightMode;
// 保存设置
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean(PREF_NIGHT_MODE, mIsNightMode).apply();
// 显示提示
Toast.makeText(this,
mIsNightMode ? R.string.night_mode_enabled : R.string.night_mode_disabled,
Toast.LENGTH_SHORT).show();
// 重启Activity以应用新主题
recreate();
}
/**
*
*/
private void applyNightMode() {
if (mIsNightMode) {
setTheme(R.style.NoteTheme_Night);
// 设置列表背景为深色
if (mNotesListView != null) {
mNotesListView.setBackgroundColor(getResources().getColor(R.color.night_list_background));
}
} else {
setTheme(R.style.NoteTheme);
// 设置列表背景为浅色
if (mNotesListView != null) {
mNotesListView.setBackgroundColor(getResources().getColor(android.R.color.white));
}
}
}
// ======================= 搜索请求 =======================
/**
@ -1355,4 +1590,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
return false;
}
/**
* onDestroy - Activity
* 广
*/
@Override
protected void onDestroy() {
super.onDestroy();
}
}

@ -67,6 +67,9 @@ public class NotesListAdapter extends CursorAdapter {
/** 选择模式标志 - true: 多选模式; false: 普通模式 */
private boolean mChoiceMode;
/** 夜间模式标志 - true: 夜间模式; false: 日间模式 */
private boolean mIsNightMode;
// ======================= 小部件属性内部类 =======================
@ -91,6 +94,16 @@ public class NotesListAdapter extends CursorAdapter {
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0; // 初始便签数为0
mIsNightMode = false; // 初始为日间模式
}
/**
*
* @param isNightMode
*/
public void setNightMode(boolean isNightMode) {
mIsNightMode = isNightMode;
notifyDataSetChanged(); // 通知视图更新
}
// ======================= 适配器核心方法 =======================
@ -120,9 +133,9 @@ public class NotesListAdapter extends CursorAdapter {
if (view instanceof NotesListItem) {
// 创建便签数据项
NoteItemData itemData = new NoteItemData(context, cursor);
// 绑定数据到列表项
// 绑定数据到列表项,传递夜间模式状态
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
isSelectedItem(cursor.getPosition()), mIsNightMode);
}
}

@ -65,6 +65,12 @@ public class NotesListItem extends LinearLayout {
/** 联系人姓名文本 - 通话记录专用,显示联系人姓名 */
private TextView mCallName;
/** 标签容器 - 显示便签的标签 */
private LinearLayout mTagContainer;
/** 加密图标 - 显示便签是否加密 */
private ImageView mEncrypted;
/** 便签数据项 - 当前项绑定的数据 */
private NoteItemData mItemData;
@ -88,10 +94,13 @@ public class NotesListItem extends LinearLayout {
// 2. 查找并保存子视图引用
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mPinned = (ImageView) findViewById(R.id.iv_pinned_icon);
mEncrypted = (ImageView) findViewById(R.id.iv_encrypted_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);
// 查找标签容器
mTagContainer = (LinearLayout) findViewById(R.id.ll_tags);
}
// ======================= 数据绑定方法 =======================
@ -109,8 +118,9 @@ public class NotesListItem extends LinearLayout {
* @param data 便
* @param choiceMode
* @param checked
* @param isNightMode
*/
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked, boolean isNightMode) {
// 1. 处理选择模式复选框
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
// 选择模式且是便签类型:显示复选框
@ -127,20 +137,20 @@ public class NotesListItem extends LinearLayout {
// 2. 根据便签类型设置不同显示
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 情况1通话记录文件夹
bindCallRecordFolder(context, data);
bindCallRecordFolder(context, data, isNightMode);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 情况2通话记录文件夹中的便签
bindCallRecordNote(context, data);
bindCallRecordNote(context, data, isNightMode);
} else {
// 情况3普通文件夹或便签
bindNormalItem(context, data);
bindNormalItem(context, data, isNightMode);
}
// 3. 更新时间显示(所有类型都显示)
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 4. 设置背景
setBackground(data);
setBackground(data, isNightMode);
}
// ======================= 通话记录文件夹绑定 =======================
@ -150,8 +160,9 @@ public class NotesListItem extends LinearLayout {
* + 便
* @param context
* @param data
* @param isNightMode
*/
private void bindCallRecordFolder(Context context, NoteItemData data) {
private void bindCallRecordFolder(Context context, NoteItemData data, boolean isNightMode) {
// 隐藏联系人姓名
mCallName.setVisibility(View.GONE);
// 显示提醒图标(文件夹图标)
@ -163,6 +174,12 @@ public class NotesListItem extends LinearLayout {
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
// 设置文件夹图标
mAlert.setImageResource(R.drawable.call_record);
// 夜间模式:更新文本颜色
if (isNightMode) {
mTitle.setTextColor(context.getResources().getColor(R.color.night_primary_text));
mTime.setTextColor(context.getResources().getColor(R.color.night_secondary_text));
}
}
// ======================= 通话记录便签绑定 =======================
@ -172,8 +189,9 @@ public class NotesListItem extends LinearLayout {
* +
* @param context
* @param data 便
* @param isNightMode
*/
private void bindCallRecordNote(Context context, NoteItemData data) {
private void bindCallRecordNote(Context context, NoteItemData data, boolean isNightMode) {
// 显示联系人姓名
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
@ -188,6 +206,13 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
// 夜间模式:更新文本颜色
if (isNightMode) {
mCallName.setTextColor(context.getResources().getColor(R.color.night_primary_text));
mTitle.setTextColor(context.getResources().getColor(R.color.night_primary_text));
mTime.setTextColor(context.getResources().getColor(R.color.night_secondary_text));
}
}
// ======================= 普通项绑定 =======================
@ -197,8 +222,9 @@ public class NotesListItem extends LinearLayout {
*
* @param context
* @param data 便/
* @param isNightMode
*/
private void bindNormalItem(Context context, NoteItemData data) {
private void bindNormalItem(Context context, NoteItemData data, boolean isNightMode) {
// 隐藏联系人姓名
mCallName.setVisibility(View.GONE);
// 设置主标题样式
@ -210,6 +236,10 @@ public class NotesListItem extends LinearLayout {
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE); // 文件夹不显示提醒图标
// 文件夹不显示标签
if (mTagContainer != null) {
mTagContainer.setVisibility(View.GONE);
}
} else {
// 普通便签:显示格式化摘要
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
@ -226,6 +256,50 @@ public class NotesListItem extends LinearLayout {
} else {
mPinned.setVisibility(View.GONE);
}
// 根据是否加密设置加密图标
if (data.isEncrypted()) {
mEncrypted.setVisibility(View.VISIBLE);
} else {
mEncrypted.setVisibility(View.GONE);
}
// 显示标签
if (mTagContainer != null) {
if (data.hasTags()) {
// 清空现有标签
mTagContainer.removeAllViews();
// 添加新标签
for (String tag : data.getTags()) {
TextView tagView = new TextView(context);
tagView.setText(tag);
tagView.setTextAppearance(context, R.style.TextAppearanceSecondaryItem);
tagView.setBackgroundResource(R.drawable.tag_background);
tagView.setPadding(8, 4, 8, 4);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(4, 0, 4, 0);
tagView.setLayoutParams(params);
// 夜间模式:更新标签颜色和背景
if (isNightMode) {
tagView.setTextColor(context.getResources().getColor(R.color.night_tag_text));
tagView.setBackgroundColor(context.getResources().getColor(R.color.night_tag_background));
}
mTagContainer.addView(tagView);
}
mTagContainer.setVisibility(View.VISIBLE);
} else {
mTagContainer.setVisibility(View.GONE);
}
}
}
// 夜间模式:更新文本颜色
if (isNightMode) {
mTitle.setTextColor(context.getResources().getColor(R.color.night_primary_text));
mTime.setTextColor(context.getResources().getColor(R.color.night_secondary_text));
}
}
@ -239,28 +313,35 @@ public class NotesListItem extends LinearLayout {
* 2. 使
*
* @param data 便
* @param isNightMode
*/
private void setBackground(NoteItemData data) {
int id = data.getBgColorId(); // 获取背景颜色ID
if (data.getType() == Notes.TYPE_NOTE) {
// 便签类型:根据位置选择背景
if (data.isSingle() || data.isOneFollowingFolder()) {
// 单独项或文件夹后的唯一便签:单独项背景
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
// 最后一项:底部圆角背景
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 第一项或文件夹后的多个便签之一:顶部圆角背景
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
private void setBackground(NoteItemData data, boolean isNightMode) {
if (isNightMode) {
// 夜间模式:使用统一的深色背景
setBackgroundColor(getContext().getResources().getColor(R.color.night_note_background));
} else {
// 日间模式:根据便签类型和位置选择背景
int id = data.getBgColorId(); // 获取背景颜色ID
if (data.getType() == Notes.TYPE_NOTE) {
// 便签类型:根据位置选择背景
if (data.isSingle() || data.isOneFollowingFolder()) {
// 单独项或文件夹后的唯一便签:单独项背景
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
// 最后一项:底部圆角背景
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 第一项或文件夹后的多个便签之一:顶部圆角背景
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
// 中间项:普通直角背景
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
// 中间项:普通直角背景
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
// 文件夹类型:统一使用文件夹背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
} else {
// 文件夹类型:统一使用文件夹背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}

@ -37,8 +37,10 @@ import android.content.IntentFilter; // 意图过滤器
import android.content.SharedPreferences; // 偏好设置
import android.os.Bundle; // 状态保存
// Android偏好设置
import android.preference.CheckBoxPreference; // 复选框偏好设置
import android.preference.Preference; // 偏好设置项
import android.preference.Preference.OnPreferenceClickListener; // 偏好设置点击监听
import android.preference.Preference.OnPreferenceChangeListener; // 偏好设置变化监听
import android.preference.PreferenceActivity; // 偏好设置Activity基类
import android.preference.PreferenceCategory; // 偏好设置分类
import android.preference.PreferenceManager; // 偏好设置管理器
@ -141,7 +143,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter,Context.RECEIVER_EXPORTED);
registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
mOriAccounts = null; // 初始化原始账户列表
// 添加设置界面头部

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="#E0E0E0" />
<stroke android:width="1dp" android:color="#BDBDBD" />
</shape>

@ -59,6 +59,15 @@
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
</LinearLayout>
<!-- 标签容器 -->
<LinearLayout
android:id="@+id/ll_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="4dp"
android:visibility="gone"
/>
</LinearLayout>
<CheckBox
@ -81,6 +90,12 @@
android:layout_height="wrap_content"
android:src="@drawable/selected"
android:visibility="gone"/>
<ImageView
android:id="@+id/iv_encrypted_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_lock_idle_lock"
android:visibility="gone"/>
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"

@ -52,6 +52,12 @@
<item
android:id="@+id/menu_insert_image"
android:title="@string/menu_insert_image" />
<item
android:id="@+id/menu_tags"
android:title="@string/menu_tags" />
<item
android:id="@+id/menu_password"
android:title="@string/menu_password" />
</menu>

@ -36,4 +36,16 @@
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"/>
<item
android:id="@+id/menu_sort"
android:title="Sort"/>
<item
android:id="@+id/menu_tags"
android:title="Tags"/>
<item
android:id="@+id/menu_night_mode"
android:title="@string/menu_night_mode"/>
</menu>

@ -47,13 +47,41 @@
<color name="user_query_highlight">#335b5b5b</color>
<!--
======================= 便签背景颜色 =======================
名称note_background
颜色值:#ffffff
功能:便签的默认背景颜色
使用场景:
1. 便签编辑界面背景
2. 便签列表项背景
-->
======================= 便签背景颜色 =======================
名称note_background
颜色值:#ffffff
功能:便签的默认背景颜色
使用场景:
1. 便签编辑界面背景
2. 便签列表项背景
-->
<color name="note_background">#ffffff</color>
<!-- ======================= 夜间模式颜色 ======================= -->
<!-- 夜间模式背景色 - 纯黑色,最强对比度 -->
<color name="night_background">#000000</color>
<!-- 夜间模式主文本色 - 纯白色,最高对比度 -->
<color name="night_primary_text">#ffffff</color>
<!-- 夜间模式副文本色 - 更亮的灰色,提高可读性 -->
<color name="night_secondary_text">#d0d0d0</color>
<!-- 夜间模式列表背景色 - 稍亮的灰色,与纯黑背景形成对比 -->
<color name="night_list_background">#1a1a1a</color>
<!-- 夜间模式操作栏背景色 - 纯黑色 -->
<color name="night_action_bar_background">#000000</color>
<!-- 夜间模式便签背景色 - 明显的灰色,使便签更突出 -->
<color name="night_note_background">#2a2a2a</color>
<!-- 夜间模式标签背景色 - 深色背景,与便签背景区分 -->
<color name="night_tag_background">#3a3a3a</color>
<!-- 夜间模式标签文本色 - 浅灰色,与标签背景形成对比 -->
<color name="night_tag_text">#b0b0b0</color>
<!-- 夜间模式强调色 - 蓝色,用于高亮和交互元素 -->
<color name="night_accent_color">#2196f3</color>
</resources>

@ -132,4 +132,17 @@
<string name="search_history">Search History</string>
<string name="datetime_dialog_ok">Set</string>
<string name="datetime_dialog_cancel">Cancel</string>
<!-- 标签功能 -->
<string name="info_tag_added">标签已添加</string>
<string name="info_tag_added_failed">标签添加失败</string>
<string name="info_note_not_saved">便签未保存,无法添加标签</string>
<string name="info_tag_add_failed_with_error">标签添加失败</string>
<string name="menu_tags">标签</string>
<string name="menu_password">密码保护</string>
<!-- 夜间模式 -->
<string name="menu_night_mode">夜间模式</string>
<string name="night_mode_enabled">夜间模式已开启</string>
<string name="night_mode_disabled">夜间模式已关闭</string>
</resources>

@ -305,6 +305,47 @@
<item name="android:background">@android:color/white</item>
<item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item>
</style>
<!-- ======================= 夜间模式主题 ======================= -->
<style name="NoteTheme.Night" parent="@android:style/Theme.Holo">
<item name="android:actionBarStyle">@style/NoteActionBarStyle.Night</item>
<item name="android:windowBackground">@color/night_background</item>
<item name="android:textColor">@color/night_primary_text</item>
<item name="android:textColorSecondary">@color/night_secondary_text</item>
<item name="android:textColorPrimary">@color/night_primary_text</item>
<item name="android:textColorPrimaryInverse">@color/night_primary_text</item>
<item name="android:colorBackground">@color/night_background</item>
<item name="android:colorForeground">@color/night_primary_text</item>
<item name="android:colorPrimary">@color/night_action_bar_background</item>
<item name="android:colorPrimaryDark">@color/night_action_bar_background</item>
<item name="android:colorAccent">@color/night_accent_color</item> <!-- 蓝色强调色,与深色主题对比明显 -->
<item name="android:divider">@color/night_secondary_text</item>
<item name="android:listDivider">@color/night_secondary_text</item>
<item name="android:scrollbarThumbVertical">@color/night_secondary_text</item>
<item name="android:scrollbarThumbHorizontal">@color/night_secondary_text</item>
</style>
<style name="NoteActionBarStyle.Night" parent="@android:style/Widget.Holo.ActionBar.Solid">
<item name="android:visibility">visible</item>
<item name="android:background">@color/night_action_bar_background</item>
<item name="android:titleTextStyle">@style/ActionBarTitleTextStyle.Night</item>
<item name="android:subtitleTextStyle">@style/ActionBarSubtitleTextStyle.Night</item>
<item name="android:backgroundStacked">@color/night_action_bar_background</item>
<item name="android:backgroundSplit">@color/night_action_bar_background</item>
</style>
<!-- 夜间模式标题样式 -->
<style name="ActionBarTitleTextStyle.Night" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
<item name="android:textColor">@color/night_primary_text</item>
<item name="android:textSize">20sp</item>
</style>
<!-- 夜间模式副标题样式 -->
<style name="ActionBarSubtitleTextStyle.Night" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle">
<item name="android:textColor">@color/night_secondary_text</item>
<item name="android:textSize">16sp</item>
</style>
</resources>
Loading…
Cancel
Save