增加隐私锁功能

mengcheng_branch
mc19 4 weeks ago
parent df78c21640
commit a6feecbe39

@ -26,20 +26,17 @@ import android.net.Uri;
/**
*Notes便
* <P>URI便/Intent</P>
* <b>static final</b>
* @author
* @since []
*/
public class Notes {
//一、ContentProvider权威域名
public static final String AUTHORITY = "micode_notes"; //拼接URI的前缀
public static final String TAG = "Notes"; //日志显示Notes
//二、便签/文件夹/系统文件夹 类型常量
/**
* 便//
*/
public static final int TYPE_NOTE = 0; //普通文本便签类型常量为0
public static final int TYPE_FOLDER = 1; //文件夹类型常量为1
public static final int TYPE_SYSTEM = 2; //系统文件夹如回收站类型常量为2
@ -50,15 +47,16 @@ public class Notes {
* {@link Notes#ID_TEMPORARY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
//三、系统保留文件夹 ID
public static final int ID_ROOT_FOLDER = 0; //默认文件夹ID为0
public static final int ID_TEMPORARY_FOLDER = -1; //临时文件夹
public static final int ID_CALL_RECORD_FOLDER = -2; //通话记录文件夹ID保存由通话备忘功能生成的便签
public static final int ID_TRASH_FOLDER = -3;
//四、Intent是什么?
/**
* Intent
*
*/
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; //提醒时间戳
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; //便签背景颜色
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; //桌面便签小部件
@ -66,14 +64,16 @@ public class Notes {
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; //便签父文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; //通话记录时间戳
//五、桌面小部件类型常量
/**
*
*/
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
/**
*便MIME
* Android ContentResolver // URI MIME
* MIME
* AndroidContentResolver//MIME
*/
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; //文本便签MIME类型
@ -81,7 +81,6 @@ public class Notes {
public static final String IMAGE = ImageData.CONTENT_ITEM_TYPE; //图片MIME类型
}
//七、基础URI
/**
* Uri to query all notes and folders
*/
@ -93,10 +92,9 @@ public class Notes {
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
//八、数据库列名接口 —— Note 表
/**
* NoteColumns便/
* 便ID,PARENT_ID,
* NoteColumns便
* 便便IDID,
*/
public interface NoteColumns {
/**
@ -227,13 +225,29 @@ public class Notes {
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
/**
* Lock status: 0-unlocked, 1-locked
* <P> Type: INTEGER </P>
*/
public static final String LOCKED = "locked";
/**
* Lock type: 0-none, 1-password, 2-gesture
* <P> Type: INTEGER </P>
*/
public static final String LOCK_TYPE = "lock_type";
/**
* Encrypted password for privacy lock
* <P> Type: TEXT </P>
*/
public static final String ENCRYPTED_PASSWORD = "encrypted_password";
}
//九、数据库列名接口 —— Data 表
/**
* DataColumns
* 便
*/
public interface DataColumns {
/**
@ -322,8 +336,10 @@ public class Notes {
}
//十、文本便签实体常量类
//文本便签TextNote类实现DataColumns接口中声明的方法
/**
* TextNote便
* DataColumns
*/
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
@ -341,8 +357,8 @@ public class Notes {
}
/**
*便
* CallNoteDataColumns
* CallNote便
* DataColumnsTextNote
*/
public static final class CallNote implements DataColumns {
/**

@ -54,7 +54,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
*/
private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 6;
private static final int DB_VERSION = 7;
/**表明常量接口*/
@ -95,6 +95,10 @@ 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.ALERT_LATITUDE + " REAL NOT NULL DEFAULT 0," +
NoteColumns.ALERT_LONGITUDE + " REAL NOT NULL DEFAULT 0," +
NoteColumns.ALERT_RADIUS + " REAL NOT NULL DEFAULT 0," +
NoteColumns.ALERT_LOCATION_NAME + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
@ -117,7 +121,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.RICH_TEXT_FORMAT + " TEXT NOT NULL DEFAULT ''," +
DataColumns.IMAGE_PATH + " TEXT NOT NULL DEFAULT ''" +
")";
/**
@ -412,6 +418,16 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
if (oldVersion == 6) {
upgradeToV7(db);
oldVersion++;
}
if (oldVersion == 7) {
upgradeToV8(db);
oldVersion++;
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
@ -463,4 +479,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 为data表添加一个专门存储图片路径的列
db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN image_path TEXT NOT NULL DEFAULT ''");
}
//v6到v7的升级为note表新增位置提醒相关的列
private void upgradeToV7(SQLiteDatabase db) {
// 为note表添加位置提醒相关的列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LATITUDE + " REAL NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LONGITUDE + " REAL NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_RADIUS + " REAL NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LOCATION_NAME + " TEXT NOT NULL DEFAULT ''");
}
//v7到v8的升级为note表新增隐私锁相关的列
private void upgradeToV8(SQLiteDatabase db) {
// 为note表添加隐私锁相关列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCKED + " INTEGER NOT NULL DEFAULT 0"); // 锁定状态0-未锁定1-已锁定
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCK_TYPE + " INTEGER NOT NULL DEFAULT 0"); // 锁类型0-无锁1-密码锁2-手势锁
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ENCRYPTED_PASSWORD + " TEXT NOT NULL DEFAULT ''"); // 加密后的密码
}
}

@ -108,10 +108,8 @@ public class UserDatabaseHelper extends SQLiteOpenHelper {
boolean authenticated = cursor.getCount() > 0;
cursor.close();
return authenticated;
}
/**
*
* @param username
@ -128,7 +126,6 @@ public class UserDatabaseHelper extends SQLiteOpenHelper {
boolean exists = cursor.getCount() > 0;
cursor.close();
return exists;
}

@ -82,7 +82,7 @@ public class WorkingNote {
DataColumns.DATA4,
DataColumns.DATA5,
DataColumns.RICH_TEXT_FORMAT,
DataColumns.IMAGE_PATH,
DataColumns.IMAGE_PATH
};
//指定查询Note表时需要返回的字段

@ -0,0 +1,353 @@
package net.micode.notes.tool;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/**
* 便
*/
public class PrivacyLockManager {
private static final String TAG = "PrivacyLockManager";
// 锁类型常量
public static final int LOCK_TYPE_NONE = 0;
public static final int LOCK_TYPE_PASSWORD = 1;
public static final int LOCK_TYPE_GESTURE = 2;
// 锁状态常量
public static final int LOCK_STATUS_UNLOCKED = 0;
public static final int LOCK_STATUS_LOCKED = 1;
private Context mContext;
public PrivacyLockManager(Context context) {
this.mContext = context;
}
/**
* SHA256
* @param password
* @return
*/
public static String encryptPassword(String password) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (Exception ex) {
Log.e(TAG, "Error encrypting password", ex);
return "";
}
}
/**
* 便
* @param noteId 便ID
* @return
*/
public boolean isNoteLocked(long noteId) {
String[] projection = new String[]{NoteColumns.LOCKED};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean isLocked = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
isLocked = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.LOCKED)) == LOCK_STATUS_LOCKED;
}
cursor.close();
}
return isLocked;
}
/**
* 便
* @param noteId 便ID
* @param lockType
* @param passwordOrGesture 使
* @return
*/
public boolean addPrivacyLock(long noteId, int lockType, String passwordOrGesture) {
if (noteId <= 0) {
Log.e(TAG, "Invalid note ID: " + noteId);
return false;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.LOCKED, LOCK_STATUS_LOCKED);
values.put(NoteColumns.LOCK_TYPE, lockType);
if ((lockType == LOCK_TYPE_PASSWORD || lockType == LOCK_TYPE_GESTURE) && passwordOrGesture != null) {
values.put(NoteColumns.ENCRYPTED_PASSWORD, passwordOrGesture);
} else if (lockType == LOCK_TYPE_NONE) {
values.put(NoteColumns.ENCRYPTED_PASSWORD, "");
}
int rowsUpdated = mContext.getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + " = ?",
new String[]{String.valueOf(noteId)}
);
return rowsUpdated > 0;
}
/**
* 便
* @param noteId 便ID
* @return
*/
public boolean removePrivacyLock(long noteId) {
if (noteId <= 0) {
Log.e(TAG, "Invalid note ID: " + noteId);
return false;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.LOCKED, LOCK_STATUS_UNLOCKED);
values.put(NoteColumns.LOCK_TYPE, LOCK_TYPE_NONE);
values.put(NoteColumns.ENCRYPTED_PASSWORD, "");
int rowsUpdated = mContext.getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + " = ?",
new String[]{String.valueOf(noteId)}
);
return rowsUpdated > 0;
}
/**
*
* @param noteId 便ID
* @param password
* @return
*/
public boolean verifyPassword(long noteId, String password) {
String[] projection = new String[]{NoteColumns.ENCRYPTED_PASSWORD};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean isValid = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
String storedEncryptedPassword = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.ENCRYPTED_PASSWORD));
String encryptedInput = encryptPassword(password);
isValid = storedEncryptedPassword.equals(encryptedInput);
}
cursor.close();
}
return isValid;
}
/**
* 便
* @param noteId 便ID
* @return
*/
public int getLockType(long noteId) {
String[] projection = new String[]{NoteColumns.LOCK_TYPE};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
int lockType = LOCK_TYPE_NONE;
if (cursor != null) {
if (cursor.moveToFirst()) {
lockType = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.LOCK_TYPE));
}
cursor.close();
}
return lockType;
}
/**
* 便
* @param folderId ID
* @param lockType
* @param passwordOrGesture 使
* @return
*/
public boolean addPrivacyLockToFolder(long folderId, int lockType, String passwordOrGesture) {
// 首先为文件夹本身添加锁
boolean folderSuccess = addPrivacyLock(folderId, lockType, passwordOrGesture);
// 然后为文件夹内的所有便签添加锁
String[] projection = new String[]{NoteColumns.ID};
String selection = NoteColumns.PARENT_ID + " = ? AND " + NoteColumns.TYPE + " = ?";
String[] selectionArgs = new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_NOTE)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean allNotesSuccess = true;
if (cursor != null) {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.ID));
boolean noteSuccess = addPrivacyLock(noteId, lockType, passwordOrGesture);
if (!noteSuccess) {
allNotesSuccess = false;
}
}
cursor.close();
}
return folderSuccess && allNotesSuccess;
}
/**
* 便
* @param folderId ID
* @return
*/
public boolean removePrivacyLockFromFolder(long folderId) {
// 首先移除文件夹本身的锁
boolean folderSuccess = removePrivacyLock(folderId);
// 然后移除文件夹内的所有便签的锁
String[] projection = new String[]{NoteColumns.ID};
String selection = NoteColumns.PARENT_ID + " = ? AND " + NoteColumns.TYPE + " = ?";
String[] selectionArgs = new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_NOTE)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean allNotesSuccess = true;
if (cursor != null) {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.ID));
boolean noteSuccess = removePrivacyLock(noteId);
if (!noteSuccess) {
allNotesSuccess = false;
}
}
cursor.close();
}
return folderSuccess && allNotesSuccess;
}
/**
*
* @param noteId 便ID
* @param gestureSequence
* @return
*/
public boolean verifyGesture(long noteId, List<Integer> gestureSequence) {
String[] projection = new String[]{NoteColumns.ENCRYPTED_PASSWORD};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean isValid = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
String storedGestureSequence = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.ENCRYPTED_PASSWORD));
// 将手势序列转换为字符串进行比较
StringBuilder gestureBuilder = new StringBuilder();
for (int point : gestureSequence) {
gestureBuilder.append(point);
}
String inputGestureSequence = gestureBuilder.toString();
isValid = storedGestureSequence.equals(inputGestureSequence);
}
cursor.close();
}
return isValid;
}
/**
*
* @param gesturePoints
* @return
*/
public static String gestureToString(List<Integer> gesturePoints) {
StringBuilder sb = new StringBuilder();
for (int point : gesturePoints) {
sb.append(point);
}
return sb.toString();
}
/**
*
* @param gestureString
* @return
*/
public static List<Integer> stringToGesture(String gestureString) {
List<Integer> gesturePoints = new ArrayList<>();
for (char c : gestureString.toCharArray()) {
gesturePoints.add(Character.getNumericValue(c));
}
return gesturePoints;
}
}

@ -11,17 +11,33 @@ public class UserManager {
private static final String PREF_NAME = "user_prefs";
private static final String KEY_IS_LOGGED_IN = "is_logged_in";
private static final String KEY_CURRENT_USER = "current_user";
private static final String KEY_LOGIN_TIMESTAMP = "login_timestamp";
private static UserManager instance;
private static volatile UserManager instance; // 使用volatile关键字确保多线程环境下的可见性
private SharedPreferences sharedPreferences;
private Context applicationContext;
private UserManager(Context context) {
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
if (context != null) {
this.applicationContext = context.getApplicationContext();
if (this.applicationContext != null) {
this.sharedPreferences = this.applicationContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
}
}
public static synchronized UserManager getInstance(Context context) {
public static UserManager getInstance(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
// 双重检查锁定模式,确保线程安全
if (instance == null) {
instance = new UserManager(context.getApplicationContext());
synchronized (UserManager.class) {
if (instance == null) {
instance = new UserManager(context.getApplicationContext());
}
}
}
return instance;
}
@ -33,6 +49,7 @@ public class UserManager {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, true)
.putString(KEY_CURRENT_USER, username)
.putLong(KEY_LOGIN_TIMESTAMP, System.currentTimeMillis()) // 保存当前登录时间
.apply();
}
@ -50,6 +67,22 @@ public class UserManager {
return sharedPreferences.getString(KEY_CURRENT_USER, "");
}
/**
* 3
*/
public boolean isLoginValid() {
if (!isLoggedIn()) {
return false;
}
long loginTime = sharedPreferences.getLong(KEY_LOGIN_TIMESTAMP, 0);
long currentTime = System.currentTimeMillis();
// 3天 = 3 * 24 * 60 * 60 * 1000 毫秒
long threeDaysInMillis = 3L * 24 * 60 * 60 * 1000;
return (currentTime - loginTime) < threeDaysInMillis;
}
/**
* 退
*/
@ -57,6 +90,7 @@ public class UserManager {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, false)
.remove(KEY_CURRENT_USER)
.remove(KEY_LOGIN_TIMESTAMP)
.apply();
}
}

@ -0,0 +1,264 @@
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class GestureLockView extends View {
private static final int MATRIX_SIZE = 3; // 3x3矩阵
private Paint mPaint; // 画笔
private List<GesturePoint> mPoints; // 手势点集合
private List<Integer> mSelectedPoints; // 已选择的点集合
private List<GestureLine> mLines; // 连接线集合
private GesturePoint mCurrentPoint; // 当前手指位置点
private boolean mIsDrawing; // 是否正在绘制
private OnGestureCompleteListener mListener; // 手势完成监听器
public interface OnGestureCompleteListener {
void onGestureComplete(List<Integer> selectedPoints);
}
public static class GesturePoint {
public float x; // X坐标
public float y; // Y坐标
public int index; // 点的索引 (0-8)
public boolean isSelected; // 是否被选中
public GesturePoint(float x, float y, int index) {
this.x = x;
this.y = y;
this.index = index;
this.isSelected = false;
}
}
public static class GestureLine {
public GesturePoint startPoint; // 起始点
public GesturePoint endPoint; // 结束点
public GestureLine(GesturePoint start, GesturePoint end) {
this.startPoint = start;
this.endPoint = end;
}
}
public GestureLockView(Context context) {
super(context);
init();
}
public GestureLockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GestureLockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true); // 抗锯齿
mPaint.setStrokeWidth(4); // 线宽
mPoints = new ArrayList<>();
mSelectedPoints = new ArrayList<>();
mLines = new ArrayList<>();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 计算点的位置
int padding = Math.min(w, h) / 10; // 边距
int cellWidth = (w - 2 * padding) / (MATRIX_SIZE - 1); // 每个格子的宽度
int cellHeight = (h - 2 * padding) / (MATRIX_SIZE - 1); // 每个格子的高度
mPoints.clear();
for (int i = 0; i < MATRIX_SIZE; i++) {
for (int j = 0; j < MATRIX_SIZE; j++) {
float x = padding + j * cellWidth;
float y = padding + i * cellHeight;
mPoints.add(new GesturePoint(x, y, i * MATRIX_SIZE + j));
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制所有点
for (GesturePoint point : mPoints) {
drawPoint(canvas, point);
}
// 绘制连接线
for (GestureLine line : mLines) {
drawLine(canvas, line);
}
// 如果正在绘制,绘制从最后一点到当前手指位置的连线
if (mIsDrawing && mCurrentPoint != null && !mSelectedPoints.isEmpty()) {
GesturePoint lastPoint = mPoints.get(mSelectedPoints.get(mSelectedPoints.size() - 1));
mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawLine(lastPoint.x, lastPoint.y, mCurrentPoint.x, mCurrentPoint.y, mPaint);
}
}
private void drawPoint(Canvas canvas, GesturePoint point) {
float radius = Math.min(getWidth(), getHeight()) / 15f; // 点的半径
if (point.isSelected) {
// 绘制选中的点(大圆圈)
mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(point.x, point.y, radius, mPaint);
// 绘制内部小圆点
mPaint.setColor(Color.WHITE);
canvas.drawCircle(point.x, point.y, radius / 2, mPaint);
} else {
// 绘制未选中的点(圆环)
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4);
canvas.drawCircle(point.x, point.y, radius, mPaint);
}
}
private void drawLine(Canvas canvas, GestureLine line) {
mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(8);
canvas.drawLine(line.startPoint.x, line.startPoint.y,
line.endPoint.x, line.endPoint.y, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleTouchDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
handleTouchMove(x, y);
break;
case MotionEvent.ACTION_UP:
handleTouchUp();
break;
}
invalidate(); // 重绘
return true;
}
private void handleTouchDown(float x, float y) {
mSelectedPoints.clear();
mLines.clear();
mIsDrawing = true;
// 检查是否点击了某个点
for (GesturePoint point : mPoints) {
float distance = (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
if (distance < Math.min(getWidth(), getHeight()) / 10f) { // 如果在点的范围内
selectPoint(point);
break;
}
}
}
private void handleTouchMove(float x, float y) {
if (!mIsDrawing) return;
// 更新当前手指位置
mCurrentPoint = new GesturePoint(x, y, -1);
// 检查是否有新的点被经过
for (GesturePoint point : mPoints) {
if (point.isSelected) continue; // 已经选过的点不再处理
float distance = (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
if (distance < Math.min(getWidth(), getHeight()) / 10f) { // 如果在点的范围内
selectPoint(point);
break;
}
}
}
private void handleTouchUp() {
if (!mIsDrawing) return;
mIsDrawing = false;
mCurrentPoint = null;
// 通知手势完成
if (mListener != null && !mSelectedPoints.isEmpty()) {
mListener.onGestureComplete(mSelectedPoints);
}
// 重置状态
reset();
}
private void selectPoint(GesturePoint point) {
if (!point.isSelected) {
point.isSelected = true;
mSelectedPoints.add(point.index);
// 添加连接线(如果不是第一个点)
if (mSelectedPoints.size() > 1) {
int lastIndex = mSelectedPoints.get(mSelectedPoints.size() - 2);
GesturePoint lastPoint = mPoints.get(lastIndex);
mLines.add(new GestureLine(lastPoint, point));
}
}
}
private void reset() {
for (GesturePoint point : mPoints) {
point.isSelected = false;
}
mSelectedPoints.clear();
mLines.clear();
mCurrentPoint = null;
}
public void setOnGestureCompleteListener(OnGestureCompleteListener listener) {
this.mListener = listener;
}
/**
*
*/
public void clearGesture() {
reset();
invalidate();
}
/**
*
*/
public int getSelectedPointsCount() {
return mSelectedPoints.size();
}
}

@ -29,10 +29,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
private View tabIndicator;
private EditText etUsername;
private EditText etPassword;
private EditText etConfirmPassword;
private ImageView ivPasswordVisibility;
private TextView tvErrorMessage;
private Button btnAction;
private TextView tvForgotPassword;
private LinearLayout confirmPasswordLayout;
// 数据库帮助类
private UserDatabaseHelper dbHelper;
@ -51,8 +53,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_register);
// 检查是否已登录,如果已登录则直接跳转到主页面
if (userManager.isLoggedIn()) {
//通过getInstance获取已初始化的UserManager实例
UserManager userManager = UserManager.getInstance(this);
//安全调用先判空再调用方法避免null
// 检查是否已登录且在有效期内,如果已登录且在有效期内则直接跳转到主页面
if (userManager != null && userManager.isLoginValid()) {
startActivity(new Intent(this, NotesListActivity.class));
finish();
return;
@ -73,10 +79,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
tabIndicator = findViewById(R.id.tab_indicator);
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
etConfirmPassword = findViewById(R.id.et_confirm_password);
ivPasswordVisibility = findViewById(R.id.iv_password_visibility);
tvErrorMessage = findViewById(R.id.tv_error_message);
btnAction = findViewById(R.id.btn_action);
tvForgotPassword = findViewById(R.id.tv_forgot_password);
confirmPasswordLayout = findViewById(R.id.confirm_password_layout);
// 初始化数据库帮助类
dbHelper = UserDatabaseHelper.getInstance(this);
@ -147,11 +155,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
// 更新指示器位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 40; // 左侧位置
params.leftMargin = 40; // 左侧位置 (登录tab的位置)
tabIndicator.setLayoutParams(params);
btnAction.setText("登录");
tvForgotPassword.setVisibility(View.VISIBLE);
confirmPasswordLayout.setVisibility(View.GONE); // 隐藏确认密码输入框
} else {
// 注册模式
tvRegisterTab.setTextColor(getResources().getColor(android.R.color.holo_orange_dark));
@ -159,11 +168,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
// 更新指示器位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 120; // 右侧位置
params.leftMargin = 120; // 右侧位置 (注册tab的位置)
tabIndicator.setLayoutParams(params);
btnAction.setText("注册");
tvForgotPassword.setVisibility(View.GONE);
confirmPasswordLayout.setVisibility(View.VISIBLE); // 显示确认密码输入框
}
// 清除错误消息
@ -227,6 +237,7 @@ public class LoginRegisterActivity extends AppCompatActivity {
private void performRegister() {
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString().trim();
String confirmPassword = etConfirmPassword.getText().toString().trim();
// 验证输入
if (TextUtils.isEmpty(username)) {
@ -248,6 +259,16 @@ public class LoginRegisterActivity extends AppCompatActivity {
showError("密码长度不能少于6位");
return;
}
if (TextUtils.isEmpty(confirmPassword)) {
showError("请确认密码");
return;
}
if (!password.equals(confirmPassword)) {
showError("两次输入的密码不一致");
return;
}
// 检查用户名是否已存在
if (dbHelper.isUsernameExists(username)) {
@ -260,9 +281,10 @@ public class LoginRegisterActivity extends AppCompatActivity {
if (success) {
Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
// 自动切换到登录模式
// 自动切换到登录模式并清空输入框
isLoginMode = true;
updateUIForLoginMode();
clearInputFields(); // 清空输入框
} else {
showError("注册失败,请重试");
}
@ -275,6 +297,16 @@ public class LoginRegisterActivity extends AppCompatActivity {
tvErrorMessage.setText(message);
tvErrorMessage.setVisibility(View.VISIBLE);
}
/**
*
*/
private void clearInputFields() {
etUsername.setText("");
etPassword.setText("");
etConfirmPassword.setText("");
tvErrorMessage.setVisibility(View.GONE);
}
/**
*

File diff suppressed because it is too large Load Diff

@ -32,6 +32,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
@ -54,7 +55,9 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
@ -63,6 +66,7 @@ import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.UserDatabaseHelper;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
@ -72,12 +76,15 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import net.micode.notes.tool.UserManager;
import net.micode.notes.tool.PrivacyLockManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
/**
*
@ -119,6 +126,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private long mCurrentFolderId; // 当前显示的文件夹ID
private ContentResolver mContentResolver; // 内容解析器
private ModeCallback mModeCallBack; // 多选模式的回调(动作模式)
private PrivacyLockManager mPrivacyLockManager; // 隐私锁管理器
private static final String TAG = "NotesListActivity"; // 日志标签
@ -994,6 +1002,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
startPreferenceActivity(); // 跳转到设置界面
break;
}
case R.id.menu_add_user: {
showAddUserDialog(); // 添加用户
break;
}
case R.id.menu_logout: {
logout(); // 退出登录
break;
@ -1088,7 +1100,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public void onClick(DialogInterface dialog, int which) {
// 清除登录状态
UserManager userManager = UserManager.getInstance(NotesListActivity.this);
userManager.logout();
if (userManager != null) {
userManager.logout();
}
// 跳转到登录界面
Intent intent = new Intent(NotesListActivity.this, LoginRegisterActivity.class);
@ -1103,7 +1117,101 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
/**
*
*
*/
private void showAddUserDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = LayoutInflater.from(this);
View dialogView = inflater.inflate(R.layout.dialog_add_user, null);
final EditText etNewUsername = dialogView.findViewById(R.id.et_new_username);
final EditText etNewPassword = dialogView.findViewById(R.id.et_new_password);
final EditText etConfirmPassword = dialogView.findViewById(R.id.et_confirm_password);
final TextView tvError = dialogView.findViewById(R.id.tv_error_message);
builder.setView(dialogView);
builder.setTitle("添加用户");
final AlertDialog dialog = builder.create();
// 取消按钮
Button btnCancel = dialogView.findViewById(R.id.btn_cancel);
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
// 确认按钮
Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
btnConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username = etNewUsername.getText().toString().trim();
String password = etNewPassword.getText().toString().trim();
String confirmPassword = etConfirmPassword.getText().toString().trim();
// 验证输入
if (TextUtils.isEmpty(username)) {
tvError.setText("请输入用户名");
tvError.setVisibility(View.VISIBLE);
return;
}
if (username.length() < 3) {
tvError.setText("用户名长度不能少于3位");
tvError.setVisibility(View.VISIBLE);
return;
}
if (TextUtils.isEmpty(password)) {
tvError.setText("请输入密码");
tvError.setVisibility(View.VISIBLE);
return;
}
if (password.length() < 6) {
tvError.setText("密码长度不能少于6位");
tvError.setVisibility(View.VISIBLE);
return;
}
if (TextUtils.isEmpty(confirmPassword)) {
tvError.setText("请确认密码");
tvError.setVisibility(View.VISIBLE);
return;
}
if (!password.equals(confirmPassword)) {
tvError.setText("两次输入的密码不一致");
tvError.setVisibility(View.VISIBLE);
return;
}
// 检查用户名是否已存在
UserDatabaseHelper dbHelper = UserDatabaseHelper.getInstance(NotesListActivity.this);
if (dbHelper.isUsernameExists(username)) {
tvError.setText("该用户名已存在,请更换用户名");
tvError.setVisibility(View.VISIBLE);
return;
}
// 注册用户
boolean success = dbHelper.registerUser(username, password);
if (success) {
Toast.makeText(NotesListActivity.this, "添加用户成功", Toast.LENGTH_SHORT).show();
dialog.dismiss();
} else {
tvError.setText("添加用户失败,请重试");
tvError.setVisibility(View.VISIBLE);
}
}
});
dialog.show();
}
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
@ -1190,16 +1298,10 @@ 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 (mNotesListView.startActionMode(mModeCallBack) != null) {
// 启动成功,设置当前项为选中状态
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
// 提供触觉反馈(震动)
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
showPrivacyLockMenu(mFocusNoteDataItem.getId());
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
// 如果长按的是文件夹,设置上下文菜单监听器
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
@ -1207,4 +1309,240 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
return false; // 返回false让系统继续处理长按事件显示上下文菜单等
}
/**
*
* @param noteId 便ID
*/
private void showPrivacyLockMenu(long noteId) {
boolean isLocked = mPrivacyLockManager.isNoteLocked(noteId);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(isLocked ? "移除隐私锁" : "添加隐私锁");
if (isLocked) {
builder.setMessage("确定要移除隐私锁吗?");
builder.setPositiveButton("移除", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 直接移除隐私锁
if (mPrivacyLockManager.removePrivacyLock(noteId)) {
Toast.makeText(NotesListActivity.this, "成功移除隐私锁", Toast.LENGTH_SHORT).show();
// 刷新列表
mNotesListAdapter.changeCursor(null);
startAsyncNotesListQuery();
} else {
Toast.makeText(NotesListActivity.this, "移除隐私锁失败", Toast.LENGTH_SHORT).show();
}
}
});
} else {
// 显示隐私锁类型选择对话框
showPrivacyLockTypeSelectionDialog(noteId);
return; // 不需要显示确认对话框
}
builder.setNegativeButton("取消", null);
builder.show();
}
/**
*
* @param noteId 便ID
*/
private void showPrivacyLockTypeSelectionDialog(long noteId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("添加隐私锁");
String[] options;
// 检查Android版本是否支持手势锁
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
// Android 9.0 (API 28)及以上版本,支持手势锁
options = new String[]{"密码锁", "手势锁"};
} else {
// 版本低于Android 9.0 (API 28),不支持手势锁,显示提示
Toast.makeText(this, "当前设备系统版本过低,不支持手势锁功能", Toast.LENGTH_SHORT).show();
options = new String[]{"密码锁"};
}
builder.setItems(options, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 密码锁
showPasswordInputDialog(noteId);
} else if (which == 1 && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
// 手势锁
showGestureLockDialog(noteId);
}
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
/**
*
* @param noteId 便ID
*/
private void showPasswordInputDialog(long noteId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_password_input, null);
builder.setView(dialogView);
TextView tvTitle = dialogView.findViewById(R.id.tv_password_title);
EditText etPassword = dialogView.findViewById(R.id.et_password);
EditText etConfirmPassword = dialogView.findViewById(R.id.et_confirm_password);
CheckBox cbShowPassword = dialogView.findViewById(R.id.cb_show_password);
TextView tvHint = dialogView.findViewById(R.id.tv_password_hint);
TextView tvErrorMessage = dialogView.findViewById(R.id.tv_error_message);
Button btnCancel = dialogView.findViewById(R.id.btn_cancel);
Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
tvTitle.setText("设置密码锁");
AlertDialog dialog = builder.create();
// 显示/隐藏密码
cbShowPassword.setOnCheckedChangeListener((buttonView, isChecked) -> {
int inputType = isChecked ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
: InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
etPassword.setInputType(inputType);
etConfirmPassword.setInputType(inputType);
etPassword.setSelection(etPassword.getText().length());
etConfirmPassword.setSelection(etConfirmPassword.getText().length());
});
// 确认按钮点击
btnConfirm.setOnClickListener(v -> {
String password = etPassword.getText().toString().trim();
String confirmPassword = etConfirmPassword.getText().toString().trim();
// 验证密码
if (password.length() < 8) {
tvErrorMessage.setText("输入密码不得少于8位字母、数字");
tvErrorMessage.setVisibility(View.VISIBLE);
return;
}
if (!password.matches("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*#?&]{8,}$")) {
tvErrorMessage.setText("密码必须包含至少8位字母和数字");
tvErrorMessage.setVisibility(View.VISIBLE);
return;
}
if (!password.equals(confirmPassword)) {
tvErrorMessage.setText("两次输入密码不一致");
tvErrorMessage.setVisibility(View.VISIBLE);
return;
}
// 加密密码并保存
String encryptedPassword = PrivacyLockManager.encryptPassword(password);
if (mPrivacyLockManager.addPrivacyLock(noteId, PrivacyLockManager.LOCK_TYPE_PASSWORD, encryptedPassword)) {
Toast.makeText(NotesListActivity.this, "成功添加隐私锁", Toast.LENGTH_SHORT).show();
dialog.dismiss();
// 刷新列表
mNotesListAdapter.changeCursor(null);
startAsyncNotesListQuery();
} else {
Toast.makeText(NotesListActivity.this, "添加隐私锁失败", Toast.LENGTH_SHORT).show();
}
});
// 取消按钮点击
btnCancel.setOnClickListener(v -> dialog.dismiss());
dialog.show();
}
/**
*
* @param noteId 便ID
*/
private void showGestureLockDialog(long noteId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_gesture_lock, null);
builder.setView(dialogView);
TextView tvTitle = dialogView.findViewById(R.id.tv_gesture_title);
TextView tvInstruction = dialogView.findViewById(R.id.tv_gesture_instruction);
GestureLockView gestureLockView = dialogView.findViewById(R.id.gesture_lock_view);
TextView tvStatus = dialogView.findViewById(R.id.tv_gesture_status);
Button btnReset = dialogView.findViewById(R.id.btn_gesture_reset);
tvTitle.setText("设置手势锁");
tvInstruction.setText("请绘制连续手势至少4个点");
tvStatus.setText("");
AlertDialog dialog = builder.create();
// 记录当前绘制状态
final List<Integer>[] firstGesture = new List[]{null};
final int[] attemptCount = {0};
final int MAX_ATTEMPTS = 3;
// 设置手势完成监听器
gestureLockView.setOnGestureCompleteListener(selectedPoints -> {
if (firstGesture[0] == null) {
// 首次绘制
if (selectedPoints.size() < 4) {
tvStatus.setText("手势需包含至少4个连续点请重新绘制");
gestureLockView.clearGesture();
return;
}
firstGesture[0] = new ArrayList<>(selectedPoints);
tvInstruction.setText("再次绘制手势确认");
tvStatus.setText("");
gestureLockView.clearGesture();
} else {
// 二次绘制验证
if (selectedPoints.equals(firstGesture[0])) {
// 手势一致,设置成功
String gestureString = PrivacyLockManager.gestureToString(selectedPoints);
if (mPrivacyLockManager.addPrivacyLock(noteId, PrivacyLockManager.LOCK_TYPE_GESTURE, gestureString)) {
Toast.makeText(NotesListActivity.this, "成功添加隐私锁", Toast.LENGTH_SHORT).show();
dialog.dismiss();
// 刷新列表
mNotesListAdapter.changeCursor(null);
startAsyncNotesListQuery();
} else {
Toast.makeText(NotesListActivity.this, "添加隐私锁失败", Toast.LENGTH_SHORT).show();
}
} else {
// 手势不一致
attemptCount[0]++;
if (attemptCount[0] >= MAX_ATTEMPTS) {
Toast.makeText(NotesListActivity.this, "3次绘制手势不一致已取消设置", Toast.LENGTH_SHORT).show();
dialog.dismiss();
return;
}
tvStatus.setText("两次绘制手势不一致,请重试 (" + attemptCount[0] + "/" + MAX_ATTEMPTS + ")");
firstGesture[0] = null;
tvInstruction.setText("请绘制连续手势至少4个点");
gestureLockView.clearGesture();
}
}
});
// 重置按钮点击
btnReset.setOnClickListener(v -> {
gestureLockView.clearGesture();
if (firstGesture[0] != null) {
firstGesture[0] = null;
tvInstruction.setText("请绘制连续手势至少4个点");
}
tvStatus.setText("");
});
dialog.setOnCancelListener(d -> {
// 取消时不做任何操作
});
dialog.show();
}
}

@ -27,6 +27,7 @@ import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.PrivacyLockManager;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/**
@ -35,6 +36,7 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
*/
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 提醒图标(时钟或通话记录图标)
private ImageView mLock; // 锁图标
private TextView mTitle; // 标题/内容文本
private TextView mTime; // 修改时间文本
private TextView mCallName; // 通话记录联系人姓名
@ -51,6 +53,7 @@ public class NotesListItem extends LinearLayout {
inflate(context, R.layout.note_item, this);
// 初始化各个子视图组件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mLock = (ImageView) findViewById(R.id.iv_lock_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
@ -129,12 +132,15 @@ public class NotesListItem extends LinearLayout {
// 设置相对时间显示(如"2分钟前"、"昨天"等)
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 显示锁图标(如果便签被锁定)
showLockIcon(context, data);
// 根据位置和类型设置背景
setBackground(data);
}
/**
*
*
*
* @param data
*/
@ -161,6 +167,20 @@ public class NotesListItem extends LinearLayout {
}
}
/**
* 便
* @param context
* @param data
*/
private void showLockIcon(Context context, NoteItemData data) {
PrivacyLockManager privacyLockManager = new PrivacyLockManager(context);
if (privacyLockManager.isNoteLocked(data.getId())) {
mLock.setVisibility(View.VISIBLE);
} else {
mLock.setVisibility(View.GONE);
}
}
/**
*
* @return NoteItemData

Loading…
Cancel
Save