Type : INTEGER (long)
*/ public static final String VERSION = "version"; + + /** + * Whether the note is encrypted + *Type : INTEGER (0 = not encrypted, 1 = encrypted)
+ */ + public static final String IS_ENCRYPTED = "is_encrypted"; } public interface TrashColumns extends NoteColumns { diff --git a/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java index 427f3c5..e87dbbc 100644 --- a/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java @@ -30,7 +30,7 @@ import net.micode.notes.data.Notes.NoteColumns; public class NotesDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "note.db"; - private static final int DB_VERSION = 5; + private static final int DB_VERSION = 6; public interface TABLE { public static final String NOTE = "note"; @@ -38,6 +38,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { public static final String DATA = "data"; public static final String TRASH = "trash_note"; + + public static final String ENCRYPTED_NOTE_PASSWORD = "encrypted_note_password"; } private static final String TAG = "NotesDatabaseHelper"; @@ -62,7 +64,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.IS_ENCRYPTED + " INTEGER NOT NULL DEFAULT 0" + ")"; private static final String CREATE_DATA_TABLE_SQL = @@ -103,9 +106,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.IS_ENCRYPTED + " INTEGER NOT NULL DEFAULT 0," + "deleted_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)" + ")"; + private static final String CREATE_ENCRYPTED_NOTE_PASSWORD_TABLE_SQL = + "CREATE TABLE " + TABLE.ENCRYPTED_NOTE_PASSWORD + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + + "password TEXT NOT NULL," + + "FOREIGN KEY(" + NoteColumns.ID + ") REFERENCES " + TABLE.NOTE + "(" + NoteColumns.ID + ") ON DELETE CASCADE" + + ")"; + /** * Increase folder's note count when move note to the folder */ @@ -316,7 +327,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } - static synchronized NotesDatabaseHelper getInstance(Context context) { + public static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); } @@ -328,6 +339,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { createNoteTable(db); createDataTable(db); createTrashTable(db); + createEncryptedNotePasswordTable(db); } @Override @@ -357,6 +369,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { oldVersion++; } + if (oldVersion == 5) { + upgradeToV6(db); + oldVersion++; + } + if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); @@ -399,4 +416,26 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { createTrashTable(db); Log.d(TAG, "Upgraded to version 5: created trash_note table"); } + + private void upgradeToV6(SQLiteDatabase db) { + // 添加is_encrypted字段到note表 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.IS_ENCRYPTED + + " INTEGER NOT NULL DEFAULT 0"); + // 添加is_encrypted字段到trash_note表(如果表已存在) + try { + db.execSQL("ALTER TABLE " + TABLE.TRASH + " ADD COLUMN " + NoteColumns.IS_ENCRYPTED + + " INTEGER NOT NULL DEFAULT 0"); + } catch (Exception e) { + // 如果表不存在或字段已存在,忽略错误 + Log.d(TAG, "trash_note table may not exist or column already exists: " + e.getMessage()); + } + // 创建加密便签密码表 + createEncryptedNotePasswordTable(db); + Log.d(TAG, "Upgraded to version 6: added is_encrypted column and encrypted_note_password table"); + } + + public void createEncryptedNotePasswordTable(SQLiteDatabase db) { + db.execSQL(CREATE_ENCRYPTED_NOTE_PASSWORD_TABLE_SQL); + Log.d(TAG, "encrypted_note_password table has been created"); + } } diff --git a/src/Notes-master/src/net/micode/notes/model/WorkingNote.java b/src/Notes-master/src/net/micode/notes/model/WorkingNote.java index 0078965..fd05259 100644 --- a/src/Notes-master/src/net/micode/notes/model/WorkingNote.java +++ b/src/Notes-master/src/net/micode/notes/model/WorkingNote.java @@ -56,6 +56,7 @@ public class WorkingNote { private static final String TAG = "WorkingNote"; // 日志标签 private boolean mIsDeleted; // 是否已删除 private NoteSettingChangedListener mNoteSettingStatusListener; // 笔记设置变化监听器 + private String mPassword; // 密码(仅在创建时使用) @@ -234,6 +235,12 @@ public class WorkingNote { mNote.syncNote(mContext, mNoteId); + // 如果设置了密码,保存密码 + if (mPassword != null && !mPassword.isEmpty()) { + net.micode.notes.tool.PasswordUtils.savePassword(mContext, mNoteId, mPassword); + mPassword = null; // 清除密码,避免重复保存 + } + // 如果该笔记存在小部件,更新小部件内容 if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE @@ -257,15 +264,26 @@ public class WorkingNote { /** * 检查笔记是否值得保存 * 当笔记已删除、空内容且不存在于数据库中、或存在于数据库但未被修改时,不值得保存 + * 但是如果设置了加密标志或密码,即使内容为空也应该保存 * @return 如果笔记值得保存返回true,否则返回false */ private boolean isWorthSaving() { - if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) - || (existInDatabase() && !mNote.isLocalModified())) { + if (mIsDeleted) { return false; - } else { + } + // 如果设置了密码,说明用户想要创建加密便签,即使内容为空也应该保存 + if (mPassword != null && !mPassword.isEmpty()) { return true; } + // 如果便签不存在于数据库中且内容为空,且没有本地修改,不值得保存 + if (!existInDatabase() && TextUtils.isEmpty(mContent) && !mNote.isLocalModified()) { + return false; + } + // 如果便签存在于数据库中但未被修改,不值得保存 + if (existInDatabase() && !mNote.isLocalModified()) { + return false; + } + return true; } /** @@ -471,6 +489,38 @@ public class WorkingNote { return mWidgetType; } + /** + * 设置便签是否加密 + * @param encrypted 是否加密 + */ + public void setEncrypted(boolean encrypted) { + mNote.setNoteValue(NoteColumns.IS_ENCRYPTED, encrypted ? "1" : "0"); + } + + /** + * 设置密码(仅在创建时使用) + * @param password 密码 + */ + public void setPassword(String password) { + mPassword = password; + } + + /** + * 检查便签是否加密 + * @return 是否加密 + */ + public boolean isEncrypted() { + Cursor cursor = mContext.getContentResolver().query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), + new String[]{NoteColumns.IS_ENCRYPTED}, null, null, null); + boolean encrypted = false; + if (cursor != null && cursor.moveToFirst()) { + encrypted = cursor.getInt(0) == 1; + cursor.close(); + } + return encrypted; + } + /** * 笔记设置变化监听器 * 用于监听笔记设置变化事件 diff --git a/src/Notes-master/src/net/micode/notes/tool/PasswordUtils.java b/src/Notes-master/src/net/micode/notes/tool/PasswordUtils.java new file mode 100644 index 0000000..88a0e7a --- /dev/null +++ b/src/Notes-master/src/net/micode/notes/tool/PasswordUtils.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper; + +/** + * 密码管理工具类 + * 用于管理加密便签的密码 + */ +public class PasswordUtils { + private static final String TAG = "PasswordUtils"; + + /** + * 保存便签密码 + * @param context 上下文对象 + * @param noteId 便签ID + * @param password 密码 + * @return 是否保存成功 + */ + public static boolean savePassword(Context context, long noteId, String password) { + if (noteId <= 0 || password == null || password.isEmpty()) { + Log.e(TAG, "Invalid noteId or password"); + return false; + } + + SQLiteDatabase db = NotesDatabaseHelper.getInstance(context).getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(NoteColumns.ID, noteId); + values.put("password", password); + + // 先删除旧密码(如果存在) + db.delete(NotesDatabaseHelper.TABLE.ENCRYPTED_NOTE_PASSWORD, + NoteColumns.ID + "=?", new String[]{String.valueOf(noteId)}); + + // 插入新密码 + long result = db.insert(NotesDatabaseHelper.TABLE.ENCRYPTED_NOTE_PASSWORD, null, values); + return result != -1; + } + + /** + * 获取便签密码 + * @param context 上下文对象 + * @param noteId 便签ID + * @return 密码,如果不存在返回null + */ + public static String getPassword(Context context, long noteId) { + if (noteId <= 0) { + return null; + } + + SQLiteDatabase db = NotesDatabaseHelper.getInstance(context).getReadableDatabase(); + Cursor cursor = db.query(NotesDatabaseHelper.TABLE.ENCRYPTED_NOTE_PASSWORD, + new String[]{"password"}, + NoteColumns.ID + "=?", + new String[]{String.valueOf(noteId)}, + null, null, null); + + String password = null; + if (cursor != null && cursor.moveToFirst()) { + password = cursor.getString(0); + cursor.close(); + } + return password; + } + + /** + * 验证密码 + * @param context 上下文对象 + * @param noteId 便签ID + * @param inputPassword 输入的密码 + * @return 密码是否正确 + */ + public static boolean verifyPassword(Context context, long noteId, String inputPassword) { + String savedPassword = getPassword(context, noteId); + if (savedPassword == null) { + return false; + } + return savedPassword.equals(inputPassword); + } + + /** + * 删除便签密码 + * @param context 上下文对象 + * @param noteId 便签ID + * @return 是否删除成功 + */ + public static boolean deletePassword(Context context, long noteId) { + if (noteId <= 0) { + return false; + } + + SQLiteDatabase db = NotesDatabaseHelper.getInstance(context).getWritableDatabase(); + int result = db.delete(NotesDatabaseHelper.TABLE.ENCRYPTED_NOTE_PASSWORD, + NoteColumns.ID + "=?", + new String[]{String.valueOf(noteId)}); + return result > 0; + } + + /** + * 检查便签是否加密 + * @param context 上下文对象 + * @param noteId 便签ID + * @return 是否加密 + */ + public static boolean isNoteEncrypted(Context context, long noteId) { + if (noteId <= 0) { + return false; + } + + Cursor cursor = context.getContentResolver().query( + android.content.ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + new String[]{NoteColumns.IS_ENCRYPTED}, + null, null, null); + + boolean isEncrypted = false; + if (cursor != null && cursor.moveToFirst()) { + isEncrypted = cursor.getInt(0) == 1; + cursor.close(); + } + return isEncrypted; + } +} diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java index a3f1487..a9000cf 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java @@ -273,6 +273,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, bgResId); } + // 处理加密便签 + boolean isEncrypted = intent.getBooleanExtra("is_encrypted", false); + if (isEncrypted) { + String password = intent.getStringExtra("password"); + if (password != null && !password.isEmpty()) { + mWorkingNote.setEncrypted(true); + // 保存密码(需要在便签保存后) + mWorkingNote.setPassword(password); + } + } + getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java index 14f60be..d9f3f79 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java @@ -48,6 +48,7 @@ public class NoteItemData { NoteColumns.TYPE, // 笔记类型 NoteColumns.WIDGET_ID, // 小部件ID NoteColumns.WIDGET_TYPE, // 小部件类型 + NoteColumns.IS_ENCRYPTED, // 是否加密 }; /** 笔记ID列索引 */ @@ -74,6 +75,8 @@ public class NoteItemData { private static final int WIDGET_ID_COLUMN = 10; /** 小部件类型列索引 */ private static final int WIDGET_TYPE_COLUMN = 11; + /** 是否加密列索引 */ + private static final int IS_ENCRYPTED_COLUMN = 12; /** 笔记ID */ private long mId; @@ -99,6 +102,8 @@ public class NoteItemData { private int mWidgetId; /** 小部件类型 */ private int mWidgetType; + /** 是否加密 */ + private boolean mIsEncrypted; /** 联系人姓名(用于通话记录) */ private String mName; /** 电话号码(用于通话记录) */ @@ -136,6 +141,7 @@ public class NoteItemData { mType = cursor.getInt(TYPE_COLUMN); mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + mIsEncrypted = cursor.getInt(IS_ENCRYPTED_COLUMN) == 1; mPhoneNumber = ""; // 如果是通话记录文件夹中的笔记,获取电话号码和联系人姓名 @@ -364,4 +370,12 @@ public class NoteItemData { public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } + + /** + * 是否加密 + * @return true表示加密,false表示未加密 + */ + public boolean isEncrypted() { + return mIsEncrypted; + } } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java index 0478bbd..ac88f3a 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -463,12 +463,81 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } private void createNewNote() { + // 弹出对话框询问是否加密 + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.encrypt_note_dialog_title); + builder.setMessage(R.string.encrypt_note_dialog_message); + builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 用户选择加密,弹出密码输入对话框 + showPasswordInputDialog(); + } + }); + builder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 用户选择不加密,直接创建便签 + createNoteWithoutEncryption(); + } + }); + builder.show(); + } + + private void showPasswordInputDialog() { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); + final EditText etPassword = (EditText) view.findViewById(R.id.et_foler_name); + etPassword.setHint(R.string.password_hint); + etPassword.setInputType(android.text.InputType.TYPE_CLASS_TEXT | + android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD); + builder.setTitle(R.string.password_set_dialog_title); + builder.setView(view); + builder.setPositiveButton(android.R.string.ok, null); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 取消,直接创建不加密的便签 + createNoteWithoutEncryption(); + } + }); + + final Dialog dialog = builder.create(); + dialog.show(); + + final Button positive = (Button) dialog.findViewById(android.R.id.button1); + positive.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + String password = etPassword.getText().toString(); + if (TextUtils.isEmpty(password)) { + Toast.makeText(NotesListActivity.this, + R.string.password_empty, Toast.LENGTH_SHORT).show(); + return; + } + dialog.dismiss(); + // 创建加密便签 + createNoteWithEncryption(password); + } + }); + } + + private void createNoteWithoutEncryption() { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + private void createNoteWithEncryption(String password) { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + intent.putExtra("is_encrypted", true); + intent.putExtra("password", password); + this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); + } + private void batchDelete() { new AsyncTask