修复bug的代码补交 #13

Merged
pjs4euiwk merged 1 commits from zengweiran_branch into master 3 weeks ago

@ -0,0 +1,63 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
/**
* OSSConfig - OSS
*
*
*
*
* 1. https://oss.console.aliyun.com/
* 2. OSS Bucket
* 3. "概览"Endpoint
* 4. "AccessKey管理"AccessKey
*
*
* - AccessKey
* - 使RAM
* - 使STS
*/
public class OSSConfig {
// OSS服务端点 - 根据您的Bucket所在区域填写
// 示例华东1杭州https://oss-cn-hangzhou.aliyuncs.com
// 示例华北2北京https://oss-cn-beijing.aliyuncs.com
// 示例华南1深圳https://oss-cn-shenzhen.aliyuncs.com
public static final String OSS_ENDPOINT = "https://oss-cn-wuhan-lr.aliyuncs.com";
// 阿里云AccessKey ID - 在阿里云控制台的"AccessKey管理"中创建
public static final String OSS_ACCESS_KEY_ID = "LTAI5tAiNrYEtYykvN9xNn3w";
// 阿里云AccessKey Secret - 在阿里云控制台的"AccessKey管理"中创建
public static final String OSS_ACCESS_KEY_SECRET = "JnRHqdTMBIoaONvEPNB8RyypZPADaM";
// OSS存储桶名称 - 在阿里云OSS控制台创建Bucket时指定的名称
public static final String OSS_BUCKET_NAME = "mini-notes";
// OSS文件存储路径前缀 - 所有便签数据将存储在此路径下
public static final String OSS_FILE_PREFIX = "notes/";
// OSS连接超时时间毫秒
public static final int OSS_CONNECTION_TIMEOUT = 300000;
// OSS Socket超时时间毫秒
public static final int OSS_SOCKET_TIMEOUT = 300000;
// OSS最大重试次数
public static final int OSS_MAX_RETRY_COUNT = 30;
}

@ -19,10 +19,21 @@ package net.micode.notes.cloud;
import android.content.Context;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* OSSManager - OSS
@ -31,30 +42,36 @@ import java.io.InputStream;
* OSS
*
*
* OSS SDK
* 使HTTPOSS API
*/
public class OSSManager {
private static final String TAG = "OSSManager";
// OSS配置信息实际项目中应从配置文件或服务器获取
private static final String OSS_ENDPOINT = "https://oss-cn-hangzhou.aliyuncs.com";
private static final String OSS_ACCESS_KEY = "your_access_key";
private static final String OSS_SECRET_KEY = "your_secret_key";
private static final String OSS_BUCKET_NAME = "your_bucket_name";
// 文件路径前缀
private static final String OSS_FILE_PREFIX = "notes/";
private Context mContext;
private String mAccessKeyId;
private String mAccessKeySecret;
private String mBucketName;
private String mFilePrefix;
/**
*
* @param context
*/
public OSSManager(Context context) {
mContext = context;
// 模拟初始化OSS客户端
Log.d(TAG, "OSS client initialized successfully (mock)");
mContext = context.getApplicationContext();
loadOSSConfig();
}
/**
* OSS
*/
private void loadOSSConfig() {
mAccessKeyId = OSSConfig.OSS_ACCESS_KEY_ID;
mAccessKeySecret = OSSConfig.OSS_ACCESS_KEY_SECRET;
mBucketName = OSSConfig.OSS_BUCKET_NAME;
mFilePrefix = OSSConfig.OSS_FILE_PREFIX;
Log.d(TAG, "OSS config loaded - Bucket: " + mBucketName);
}
/**
@ -63,7 +80,31 @@ public class OSSManager {
* @return
*/
public String getFilePath(String username) {
return OSS_FILE_PREFIX + "notes_" + username + ".json";
return mFilePrefix + "notes_" + username + ".json";
}
/**
* OSS URL
* @param filePath
* @return URL
*/
private String buildOSSUrl(String filePath) {
try {
// 只对路径中的文件名部分进行编码,保留路径分隔符
String[] pathParts = filePath.split("/");
StringBuilder encodedPath = new StringBuilder();
for (int i = 0; i < pathParts.length; i++) {
if (i > 0) {
encodedPath.append("/");
}
encodedPath.append(URLEncoder.encode(pathParts[i], "UTF-8").replace("+", "%20"));
}
String baseUrl = "https://" + mBucketName + ".oss-cn-wuhan-lr.aliyuncs.com";
return baseUrl + "/" + encodedPath.toString();
} catch (Exception e) {
Log.e(TAG, "Failed to build OSS URL", e);
return "";
}
}
/**
@ -73,10 +114,107 @@ public class OSSManager {
* @return
*/
public boolean uploadFile(String filePath, String content) {
// 模拟上传操作
Log.d(TAG, "File uploaded successfully (mock): " + filePath);
Log.d(TAG, "Upload content length: " + content.length());
return true;
HttpURLConnection connection = null;
OutputStream outputStream = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
Log.d(TAG, "Building URL for upload: " + filePath);
Log.d(TAG, "Generated URL: " + urlStr);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("PUT");
connection.setDoOutput(true);
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
byte[] data = content.getBytes("UTF-8");
String contentMD5 = "";
String contentType = "application/json; charset=utf-8";
String date = getGMTDate();
connection.setRequestProperty("Content-Length", String.valueOf(data.length));
connection.setRequestProperty("Content-Type", contentType);
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName+ "/" + filePath;
String signature = generateSignature("PUT", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Upload URL: " + urlStr);
Log.d(TAG, "CanonicalizedResource: " + canonicalizedResource);
Log.d(TAG, "Date: " + date);
Log.d(TAG, "Signature: " + signature);
Log.d(TAG, "Content length: " + data.length);
// 尝试建立连接
Log.d(TAG, "Connecting to OSS...");
connection.connect();
// 写入数据
Log.d(TAG, "Writing data to OSS...");
outputStream = connection.getOutputStream();
outputStream.write(data);
outputStream.flush();
Log.d(TAG, "Data written successfully");
// 获取响应
Log.d(TAG, "Getting response from OSS...");
int responseCode = connection.getResponseCode();
Log.d(TAG, "Upload response code: " + responseCode);
// 获取响应消息
String responseMessage = connection.getResponseMessage();
Log.d(TAG, "Upload response message: " + responseMessage);
if (responseCode == 200 || responseCode == 201) {
Log.d(TAG, "File uploaded successfully: " + filePath);
return true;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Upload error response: " + errorResponse);
} else {
Log.e(TAG, "No error stream available for response code: " + responseCode);
// 尝试获取响应头信息
Log.e(TAG, "Response headers:");
for (int i = 0; ; i++) {
String headerName = connection.getHeaderFieldKey(i);
if (headerName == null) break;
String headerValue = connection.getHeaderField(i);
Log.e(TAG, headerName + ": " + headerValue);
}
}
Log.e(TAG, "Upload failed with response code: " + responseCode + ", message: " + responseMessage);
return false;
}
} catch (IOException e) {
Log.e(TAG, "IO error during upload", e);
return false;
} catch (Exception e) {
Log.e(TAG, "OSS error during upload", e);
return false;
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
@ -85,9 +223,85 @@ public class OSSManager {
* @return null
*/
public String downloadFile(String filePath) {
// 模拟下载操作,返回空数据
Log.d(TAG, "File downloaded successfully (mock): " + filePath);
return "{\"user\":\"test\",\"sync_time\":1620000000000,\"notes\":[]}";
HttpURLConnection connection = null;
InputStream inputStream = null;
InputStream errorStream = null;
BufferedReader reader = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
String contentType = "";
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName+ "/" + filePath;
String signature = generateSignature("GET", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Download URL: " + urlStr);
Log.d(TAG, "CanonicalizedResource: " + canonicalizedResource);
Log.d(TAG, "Date: " + date);
Log.d(TAG, "Signature: " + signature);
int responseCode = connection.getResponseCode();
Log.d(TAG, "Download response code: " + responseCode);
if (responseCode == 200) {
inputStream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line);
}
Log.d(TAG, "File downloaded successfully: " + filePath);
Log.d(TAG, "Content length: " + content.length());
return content.toString();
} else if (responseCode == 404) {
Log.d(TAG, "File does not exist in OSS: " + filePath);
return null;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Download error response: " + errorResponse);
}
Log.e(TAG, "Download failed with response code: " + responseCode);
return null;
}
} catch (Exception e) {
Log.e(TAG, "OSS error during download", e);
return null;
} finally {
try {
if (reader != null) {
reader.close();
}
if (inputStream != null) {
inputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
@ -96,16 +310,299 @@ public class OSSManager {
* @return
*/
public boolean doesFileExist(String filePath) {
// 模拟文件存在检查
Log.d(TAG, "File exists check (mock): " + filePath + " = true");
return true;
HttpURLConnection connection = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD");
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
String contentType = "";
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName+ "/" + filePath;
String signature = generateSignature("HEAD", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
int responseCode = connection.getResponseCode();
boolean exists = (responseCode == 200);
Log.d(TAG, "File exists check: " + filePath + " = " + exists + " (response code: " + responseCode + ")");
if (!exists && responseCode != 404) {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Existence check error response: " + errorResponse);
}
}
return exists;
} catch (Exception e) {
Log.e(TAG, "OSS error during existence check", e);
return false;
} finally {
try {
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
* OSS
*/
public void release() {
// 模拟释放资源
Log.d(TAG, "OSS client released (mock)");
Log.d(TAG, "OSS client released");
}
/**
* OSS
* @return
*/
public boolean isConfigValid() {
return !mAccessKeyId.equals("your_access_key_id_here") &&
!mAccessKeySecret.equals("your_access_key_secret_here") &&
!mBucketName.equals("your_bucket_name_here");
}
/**
* GMT
* @return GMT
*/
private String getGMTDate() {
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return dateFormat.format(new Date());
}
/**
* OSS
* @param method HTTP
* @param contentMD5 Content-MD5
* @param contentType Content-Type
* @param date Date
* @param canonicalizedResource
* @return
*/
private String generateSignature(String method, String contentMD5, String contentType, String date, String canonicalizedResource) {
try {
String stringToSign = method + "\n" +
(contentMD5 != null ? contentMD5 : "") + "\n" +
(contentType != null ? contentType : "") + "\n" +
date + "\n" +
canonicalizedResource;
Log.d(TAG, "String to sign: \"" + stringToSign.replace("\n", "\\n") + "\"");
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKeySpec = new SecretKeySpec(mAccessKeySecret.getBytes("UTF-8"), "HmacSHA1");
mac.init(secretKeySpec);
byte[] signatureBytes = mac.doFinal(stringToSign.getBytes("UTF-8"));
String signature = new String(android.util.Base64.encode(signatureBytes, android.util.Base64.NO_WRAP));
return signature;
} catch (Exception e) {
Log.e(TAG, "Failed to generate signature", e);
return "";
}
}
/**
*
* @param inputStream
* @return
*/
private String readStream(InputStream inputStream) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} catch (Exception e) {
Log.e(TAG, "Failed to read stream", e);
return "";
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
Log.e(TAG, "Error closing reader", e);
}
}
}
/**
* OSS
* @param filePath
* @param data
* @param contentType
* @return
*/
public boolean uploadBinaryFile(String filePath, byte[] data, String contentType) {
HttpURLConnection connection = null;
OutputStream outputStream = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("PUT");
connection.setDoOutput(true);
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
connection.setRequestProperty("Content-Length", String.valueOf(data.length));
connection.setRequestProperty("Content-Type", contentType);
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName + "/" + filePath;
String signature = generateSignature("PUT", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Upload binary file URL: " + urlStr);
Log.d(TAG, "Content length: " + data.length);
connection.connect();
outputStream = connection.getOutputStream();
outputStream.write(data);
outputStream.flush();
int responseCode = connection.getResponseCode();
Log.d(TAG, "Upload binary file response code: " + responseCode);
if (responseCode == 200 || responseCode == 201) {
Log.d(TAG, "Binary file uploaded successfully: " + filePath);
return true;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Upload binary file error response: " + errorResponse);
}
Log.e(TAG, "Upload binary file failed with response code: " + responseCode);
return false;
}
} catch (Exception e) {
Log.e(TAG, "Failed to upload binary file", e);
return false;
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
* OSS
* @param filePath
* @return null
*/
public byte[] downloadBinaryFile(String filePath) {
HttpURLConnection connection = null;
InputStream inputStream = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
String contentType = "";
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName + "/" + filePath;
String signature = generateSignature("GET", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Download binary file URL: " + urlStr);
int responseCode = connection.getResponseCode();
Log.d(TAG, "Download binary file response code: " + responseCode);
if (responseCode == 200) {
inputStream = connection.getInputStream();
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len;
while ((len = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
byte[] data = baos.toByteArray();
Log.d(TAG, "Binary file downloaded successfully: " + filePath + ", size: " + data.length);
return data;
} else if (responseCode == 404) {
Log.d(TAG, "Binary file does not exist in OSS: " + filePath);
return null;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Download binary file error response: " + errorResponse);
}
Log.e(TAG, "Download binary file failed with response code: " + responseCode);
return null;
}
} catch (Exception e) {
Log.e(TAG, "Failed to download binary file", e);
return null;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
}
}

@ -0,0 +1,132 @@
/*
* 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.cloud;
import android.content.Context;
import android.app.AlertDialog;
import android.content.DialogInterface;
/**
* SyncDialogManager -
*
*
*
*
* UI
*/
public class SyncDialogManager {
private static final String TAG = "SyncDialogManager";
/**
*
*/
public interface SyncOptionListener {
/**
*
*/
void onUploadSelected();
/**
*
*/
void onDownloadSelected();
/**
*
*/
void onCanceled();
}
/**
*
*/
public interface ConfirmListener {
/**
*
*/
void onConfirm();
/**
*
*/
void onCancel();
}
/**
*
* @param context
* @param listener
*/
public static void showSyncOptionsDialog(Context context, final SyncOptionListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("同步操作选择")
.setMessage("请选择要执行的同步操作")
.setNegativeButton("下载", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onDownloadSelected();
}
}
})
.setPositiveButton("上传", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onUploadSelected();
}
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (listener != null) {
listener.onCanceled();
}
}
})
.show();
}
/**
*
* @param context
* @param listener
*/
public static void showUploadConfirmDialog(Context context, final ConfirmListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("上传确认")
.setMessage("上传会覆盖云端内容,确定要继续吗?")
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onCancel();
}
}
})
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onConfirm();
}
}
})
.show();
}
}

@ -17,11 +17,22 @@
package net.micode.notes.cloud;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.account.AccountManager;
import net.micode.notes.data.AttachmentManager;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.NoteSyncUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
/**
* SyncManager -
*
@ -89,6 +100,15 @@ public class SyncManager {
return;
}
// 检查OSS配置是否有效
if (!mOssManager.isConfigValid()) {
Log.w(TAG, "OSS configuration is invalid");
if (callback != null) {
callback.onSyncFailed("OSS配置无效请检查配置文件");
}
return;
}
// 检查用户登录状态
if (!AccountManager.isUserLoggedIn(mContext)) {
Log.w(TAG, "User not logged in");
@ -125,7 +145,31 @@ public class SyncManager {
return false;
}
// 上传到OSS
// 解析JSON获取附件列表
JSONObject root = new JSONObject(jsonContent);
JSONArray notesArray = root.optJSONArray("notes");
if (notesArray != null) {
for (int i = 0; i < notesArray.length(); i++) {
JSONObject noteObj = notesArray.optJSONObject(i);
if (noteObj != null) {
JSONArray attachmentsArray = noteObj.optJSONArray("attachments");
if (attachmentsArray != null) {
for (int j = 0; j < attachmentsArray.length(); j++) {
JSONObject attachmentObj = attachmentsArray.optJSONObject(j);
if (attachmentObj != null) {
String filePath = attachmentObj.optString("file_path", "");
if (!filePath.isEmpty()) {
// 上传附件文件到OSS
uploadAttachmentFile(username, filePath);
}
}
}
}
}
}
}
// 上传JSON数据到OSS
String filePath = mOssManager.getFilePath(username);
boolean success = mOssManager.uploadFile(filePath, jsonContent);
Log.d(TAG, "Upload notes result: " + success);
@ -136,6 +180,56 @@ public class SyncManager {
}
}
/**
* OSS
* @param username
* @param localFilePath
* @return
*/
private boolean uploadAttachmentFile(String username, String localFilePath) {
try {
File file = new File(localFilePath);
if (!file.exists()) {
Log.w(TAG, "Attachment file does not exist: " + localFilePath);
return false;
}
// 读取文件内容
java.io.FileInputStream fis = new java.io.FileInputStream(file);
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
fis.close();
byte[] data = baos.toByteArray();
// 构建OSS文件路径
String fileName = file.getName();
String ossFilePath = OSSConfig.OSS_FILE_PREFIX + "attachments/" + username + "/" + fileName;
// 上传到OSS
String contentType = "image/jpeg"; // 默认图片类型
if (fileName.toLowerCase().endsWith(".png")) {
contentType = "image/png";
} else if (fileName.toLowerCase().endsWith(".gif")) {
contentType = "image/gif";
}
boolean success = mOssManager.uploadBinaryFile(ossFilePath, data, contentType);
if (success) {
Log.d(TAG, "Attachment uploaded successfully: " + ossFilePath);
} else {
Log.e(TAG, "Failed to upload attachment: " + ossFilePath);
}
return success;
} catch (Exception e) {
Log.e(TAG, "Failed to upload attachment file: " + localFilePath, e);
return false;
}
}
/**
*
* @param username
@ -152,6 +246,30 @@ public class SyncManager {
return false;
}
// 解析JSON获取附件列表
JSONObject root = new JSONObject(jsonContent);
JSONArray notesArray = root.optJSONArray("notes");
if (notesArray != null) {
for (int i = 0; i < notesArray.length(); i++) {
JSONObject noteObj = notesArray.optJSONObject(i);
if (noteObj != null) {
JSONArray attachmentsArray = noteObj.optJSONArray("attachments");
if (attachmentsArray != null) {
for (int j = 0; j < attachmentsArray.length(); j++) {
JSONObject attachmentObj = attachmentsArray.optJSONObject(j);
if (attachmentObj != null) {
String filePath2 = attachmentObj.optString("file_path", "");
if (!filePath2.isEmpty()) {
// 下载附件文件从OSS
downloadAttachmentFile(username, filePath2);
}
}
}
}
}
}
}
// 解析并合并数据
boolean success = NoteSyncUtils.jsonToLocalNotes(mContext, jsonContent);
Log.d(TAG, "Download notes result: " + success);
@ -162,6 +280,49 @@ public class SyncManager {
}
}
/**
* OSS
* @param username
* @param originalFilePath
* @return
*/
private boolean downloadAttachmentFile(String username, String originalFilePath) {
try {
// 获取文件名
File originalFile = new File(originalFilePath);
String fileName = originalFile.getName();
// 构建OSS文件路径
String ossFilePath = OSSConfig.OSS_FILE_PREFIX + "attachments/" + username + "/" + fileName;
// 从OSS下载文件
byte[] data = mOssManager.downloadBinaryFile(ossFilePath);
if (data == null) {
Log.w(TAG, "Attachment not found in OSS: " + ossFilePath);
return false;
}
// 获取附件存储目录
AttachmentManager attachmentManager = new AttachmentManager(mContext);
File storageDir = attachmentManager.getAttachmentStorageDir();
if (!storageDir.exists()) {
storageDir.mkdirs();
}
// 保存文件到本地
File destFile = new File(storageDir, fileName);
FileOutputStream fos = new FileOutputStream(destFile);
fos.write(data);
fos.close();
Log.d(TAG, "Attachment downloaded successfully: " + destFile.getAbsolutePath());
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to download attachment file: " + originalFilePath, e);
return false;
}
}
/**
*
*/
@ -182,4 +343,152 @@ public class SyncManager {
*/
void onSyncFailed(String errorMessage);
}
/**
*
* @param callback
*/
public void triggerUpload(final SyncCallback callback) {
if (mIsSyncing) {
Log.d(TAG, "Upload already in progress");
if (callback != null) {
callback.onSyncFailed("上传已在进行中");
}
return;
}
// 检查OSS配置是否有效
if (!mOssManager.isConfigValid()) {
Log.w(TAG, "OSS configuration is invalid");
if (callback != null) {
callback.onSyncFailed("OSS配置无效请检查配置文件");
}
return;
}
// 检查用户登录状态
if (!AccountManager.isUserLoggedIn(mContext)) {
Log.w(TAG, "User not logged in");
if (callback != null) {
callback.onSyncFailed("请先登录后再同步");
}
return;
}
final String username = AccountManager.getCurrentUser(mContext);
if (username.isEmpty()) {
Log.w(TAG, "Empty username");
if (callback != null) {
callback.onSyncFailed("用户名为空");
}
return;
}
// 设置同步状态
setSyncing(true);
// 通知同步开始
if (callback != null) {
callback.onSyncStart();
}
// 在后台执行上传操作
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean success = uploadNotes(username);
setSyncing(false);
if (callback != null) {
if (success) {
callback.onSyncSuccess();
} else {
callback.onSyncFailed("上传失败,请重试");
}
}
} catch (Exception e) {
Log.e(TAG, "Upload failed", e);
setSyncing(false);
if (callback != null) {
callback.onSyncFailed("上传失败:" + e.getMessage());
}
}
}
}).start();
}
/**
*
* @param callback
*/
public void triggerDownload(final SyncCallback callback) {
if (mIsSyncing) {
Log.d(TAG, "Download already in progress");
if (callback != null) {
callback.onSyncFailed("下载已在进行中");
}
return;
}
// 检查OSS配置是否有效
if (!mOssManager.isConfigValid()) {
Log.w(TAG, "OSS configuration is invalid");
if (callback != null) {
callback.onSyncFailed("OSS配置无效请检查配置文件");
}
return;
}
// 检查用户登录状态
if (!AccountManager.isUserLoggedIn(mContext)) {
Log.w(TAG, "User not logged in");
if (callback != null) {
callback.onSyncFailed("请先登录后再同步");
}
return;
}
final String username = AccountManager.getCurrentUser(mContext);
if (username.isEmpty()) {
Log.w(TAG, "Empty username");
if (callback != null) {
callback.onSyncFailed("用户名为空");
}
return;
}
// 设置同步状态
setSyncing(true);
// 通知同步开始
if (callback != null) {
callback.onSyncStart();
}
// 在后台执行下载操作
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean success = downloadNotes(username);
setSyncing(false);
if (callback != null) {
if (success) {
callback.onSyncSuccess();
} else {
callback.onSyncFailed("下载失败,请重试");
}
}
} catch (Exception e) {
Log.e(TAG, "Download failed", e);
setSyncing(false);
if (callback != null) {
callback.onSyncFailed("下载失败:" + e.getMessage());
}
}
}
}).start();
}
}

@ -104,7 +104,7 @@ public class SyncTask extends AsyncTask<Void, Integer, SyncTask.SyncResult> {
boolean uploadSuccess = mSyncManager.uploadNotes(mUsername);
if (!uploadSuccess) {
Log.e(TAG, "Upload failed");
return new SyncResult(false, "上传失败,请重试");
return new SyncResult(false, "上传到云端失败");
}
publishProgress(100); // 100% 进度
@ -112,7 +112,7 @@ public class SyncTask extends AsyncTask<Void, Integer, SyncTask.SyncResult> {
return new SyncResult(true, null);
} catch (Exception e) {
Log.e(TAG, "Sync task failed", e);
return new SyncResult(false, "同步失败,请重试");
return new SyncResult(false, "OSS操作失败请稍后重试");
}
}
@ -149,4 +149,4 @@ public class SyncTask extends AsyncTask<Void, Integer, SyncTask.SyncResult> {
Log.d(TAG, "Sync task finished with result: " + result.success);
}
}
}

@ -115,7 +115,7 @@ 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_LOCKED + " INTEGER NOT NULL DEFAULT 0" +
")";

@ -387,7 +387,8 @@ public class Note {
return null;
}
}
return null;
// 如果只有插入操作(没有更新操作),也返回成功
return ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
}
}
}

@ -32,6 +32,7 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class DataUtils {
@ -112,12 +113,30 @@ public class DataUtils {
return true;
}
// 先查询所有笔记的当前parent_id用于记录原始文件夹
HashMap<Long, Long> noteToParentMap = new HashMap<>();
for (long id : ids) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
new String[]{NoteColumns.PARENT_ID}, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
noteToParentMap.put(id, cursor.getLong(0));
}
cursor.close();
}
}
// 构建批量更新操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId); // 更新父文件夹ID
// 记录原始父文件夹ID如果查询到的话
Long originParentId = noteToParentMap.get(id);
if (originParentId != null) {
builder.withValue(NoteColumns.ORIGIN_PARENT_ID, originParentId);
}
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 标记为已本地修改
operationList.add(builder.build());
}

@ -28,12 +28,15 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.AttachmentManager;
import net.micode.notes.model.Note;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -149,6 +152,21 @@ public class NoteSyncUtils {
}
noteObj.put("data", dataObj);
// 附件信息
JSONArray attachmentsArray = new JSONArray();
AttachmentManager attachmentManager = new AttachmentManager(context);
List<AttachmentManager.Attachment> attachments = attachmentManager.getAttachmentsByNoteId(noteId);
for (AttachmentManager.Attachment attachment : attachments) {
JSONObject attachmentObj = new JSONObject();
attachmentObj.put("id", attachment.id);
attachmentObj.put("type", attachment.type);
attachmentObj.put("file_path", attachment.filePath);
attachmentObj.put("created_time", attachment.createdTime);
attachmentsArray.put(attachmentObj);
}
noteObj.put("attachments", attachmentsArray);
return noteObj;
} catch (Exception e) {
Log.e(TAG, "Failed to create note JSON", e);
@ -225,18 +243,117 @@ public class NoteSyncUtils {
*/
private static void createLocalNote(Context context, JSONObject noteObj) {
try {
// 创建笔记基本信息
ContentResolver resolver = context.getContentResolver();
Log.d(TAG, "Creating new note from cloud: " + noteObj.toString());
// 注意这里需要使用Note类的方法来创建笔记因为需要处理数据关联
// 简化实现,直接插入数据
// 获取笔记基本信息
long parentId = noteObj.optLong("parent_id", Notes.ID_ROOT_FOLDER);
long createdDate = noteObj.optLong("created_date", System.currentTimeMillis());
long modifiedDate = noteObj.optLong("modified_date", System.currentTimeMillis());
long alertDate = noteObj.optLong("alert_date", 0);
String snippet = noteObj.optString("snippet", "");
int bgColorId = noteObj.optInt("bg_color_id", 0);
int hasAttachment = noteObj.optInt("has_attachment", 0);
int isLocked = noteObj.optInt("is_locked", 0);
// 创建笔记记录
// 实际项目中应使用Note类的syncNote方法
Log.d(TAG, "Creating new note from cloud: " + noteObj.toString());
// 获取笔记内容
JSONObject dataObj = noteObj.optJSONObject("data");
String noteType = dataObj.optString("type", "text");
// 创建新笔记
long noteId = Note.getNewNoteId(context, parentId);
Log.d(TAG, "Created new note with id: " + noteId);
// 创建Note对象
Note note = new Note();
// 设置笔记基本信息
note.setNoteValue(NoteColumns.CREATED_DATE, String.valueOf(createdDate));
note.setNoteValue(NoteColumns.MODIFIED_DATE, String.valueOf(modifiedDate));
note.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(alertDate));
note.setNoteValue(NoteColumns.SNIPPET, snippet);
note.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(bgColorId));
note.setNoteValue(NoteColumns.HAS_ATTACHMENT, String.valueOf(hasAttachment));
note.setNoteValue(NoteColumns.IS_LOCKED, String.valueOf(isLocked));
// 设置笔记内容
if ("text".equals(noteType)) {
// 文本笔记
String content = dataObj.optString("content", "");
int mode = dataObj.optInt("mode", 0);
note.setTextData(TextNote.CONTENT, content);
note.setTextData(TextNote.MODE, String.valueOf(mode));
Log.d(TAG, "Set text note content: " + content);
} else if ("call".equals(noteType)) {
// 通话笔记
String content = dataObj.optString("content", "");
long callDate = dataObj.optLong("call_date", 0);
String phoneNumber = dataObj.optString("phone_number", "");
note.setCallData(CallNote.CONTENT, content);
note.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
note.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
Log.d(TAG, "Set call note content: " + content + ", phone: " + phoneNumber);
}
// 同步笔记到数据库
boolean success = note.syncNote(context, noteId);
Log.d(TAG, "Sync note result: " + success);
if (success) {
Log.d(TAG, "Note created successfully from cloud: " + noteId);
// 恢复附件信息
JSONArray attachmentsArray = noteObj.optJSONArray("attachments");
if (attachmentsArray != null && attachmentsArray.length() > 0) {
Log.d(TAG, "Restoring " + attachmentsArray.length() + " attachments for note: " + noteId);
for (int i = 0; i < attachmentsArray.length(); i++) {
JSONObject attachmentObj = attachmentsArray.optJSONObject(i);
if (attachmentObj != null) {
// 创建附件记录附件文件需要从OSS下载
String filePath = attachmentObj.optString("file_path", "");
int type = attachmentObj.optInt("type", 0);
long createdTime = attachmentObj.optLong("created_time", System.currentTimeMillis());
// 注意这里只记录附件元数据实际的文件下载由SyncManager处理
// 文件下载后会更新filePath
createAttachmentRecord(context, noteId, type, filePath, createdTime);
}
}
}
} else {
Log.e(TAG, "Failed to sync note to local database: " + noteId);
}
} catch (Exception e) {
Log.e(TAG, "Failed to create local note", e);
}
}
/**
*
* @param context
* @param noteId ID
* @param type
* @param filePath
* @param createdTime
*/
private static void createAttachmentRecord(Context context, long noteId, int type, String filePath, long createdTime) {
try {
android.content.ContentValues values = new android.content.ContentValues();
values.put(Notes.AttachmentColumns.NOTE_ID, noteId);
values.put(Notes.AttachmentColumns.TYPE, type);
values.put(Notes.AttachmentColumns.FILE_PATH, filePath);
values.put(Notes.AttachmentColumns.CREATED_TIME, createdTime);
android.net.Uri uri = context.getContentResolver().insert(Notes.CONTENT_ATTACHMENT_URI, values);
if (uri != null) {
Log.d(TAG, "Created attachment record: " + uri + " for note: " + noteId);
} else {
Log.e(TAG, "Failed to create attachment record for note: " + noteId);
}
} catch (Exception e) {
Log.e(TAG, "Failed to create attachment record", e);
}
}
}

@ -191,6 +191,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private static final int REQUEST_CAMERA_PREVIEW = 1005;
private String mCurrentPhotoPath; // 相机拍照临时文件路径
// 密码验证标志
private boolean mPasswordVerified = false; // 标记密码是否已验证通过
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -358,8 +361,11 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
if (TextUtils.equals(Intent.ACTION_VIEW, action)) {
// 检查便签是否加锁
if (isNoteLocked()) {
// 便签加锁需要验证密码此时不设置mPasswordVerified标志
showPasswordDialogForLockedNote();
} else {
// 未加锁便签,标记密码已验证(无需验证)
mPasswordVerified = true;
// 隐藏软键盘
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
@ -370,6 +376,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
loadAttachments();
}
} else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, action)) {
// 新建或编辑模式,标记密码已验证(新建便签无需密码)
mPasswordVerified = true;
// 显示软键盘
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
@ -412,6 +420,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
/**
* 便
*
*/
private void showPasswordDialogForLockedNote() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@ -432,7 +441,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
return;
}
if (PasswordManager.verifyPassword(NoteEditActivity.this, password)) {
// 密码验证通过初始化UI
// 密码验证通过设置标志位并初始化UI
mPasswordVerified = true;
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
@ -450,15 +460,168 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
finish();
}
});
builder.show();
builder.setCancelable(false);
// 添加"忘记密码"按钮,点击后通过密保问题验证重置密码
builder.setNeutralButton(R.string.btn_forgot_password, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User clicked forgot password button, showing security questions dialog");
showSecurityQuestionsForNoteUnlock();
}
});
AlertDialog dialog = builder.create();
dialog.setCancelable(false);
dialog.show();
}
/**
* 便
* 便
*/
private void showSecurityQuestionsForNoteUnlock() {
// 检查是否设置了密保问题
if (!NotesPreferenceActivity.hasSecurityQuestionsSet(this)) {
Log.w(TAG, "Security questions not set, cannot reset password");
Toast.makeText(this, "请先在设置中设置密保问题", Toast.LENGTH_SHORT).show();
finish();
return;
}
Log.d(TAG, "Showing security questions dialog for note unlock");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_forgot_password);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage("请回答密保问题以重置密码");
// 创建布局
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
// 创建姓名输入框
final EditText nameInput = new EditText(this);
nameInput.setHint("请输入姓名");
layout.addView(nameInput);
// 创建生日输入框
final EditText birthdayInput = new EditText(this);
birthdayInput.setHint("请输入生日 (YYYY-MM-DD)");
layout.addView(birthdayInput);
builder.setView(layout);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = nameInput.getText().toString().trim();
String birthday = birthdayInput.getText().toString().trim();
Log.d(TAG, "Verifying security questions - name: " + name + ", birthday: " + birthday);
if (NotesPreferenceActivity.verifySecurityQuestions(NoteEditActivity.this, name, birthday)) {
Log.i(TAG, "Security questions verified successfully, showing reset password dialog");
// 验证成功,显示设置新密码对话框
showResetPasswordForNoteUnlock();
} else {
Log.w(TAG, "Security questions verification failed");
Toast.makeText(NoteEditActivity.this, "密保问题回答错误", Toast.LENGTH_SHORT).show();
finish();
}
}
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User cancelled security questions dialog");
finish();
}
});
AlertDialog dialog = builder.create();
dialog.setCancelable(false);
dialog.show();
}
/**
* 便
* 便
*/
private void showResetPasswordForNoteUnlock() {
Log.d(TAG, "Showing reset password dialog for note unlock");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("设置新密码");
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setMessage("验证成功,请设置新密码");
// 创建密码输入框
final EditText passwordInput = new EditText(this);
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordInput.setHint("请输入新密码");
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
layout.addView(passwordInput);
builder.setView(layout);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String newPassword = passwordInput.getText().toString().trim();
if (newPassword.isEmpty()) {
Log.w(TAG, "New password is empty");
Toast.makeText(NoteEditActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
finish();
return;
}
Log.i(TAG, "Setting new password for note unlock");
// 设置新密码
if (PasswordManager.setPassword(NoteEditActivity.this, newPassword)) {
Log.i(TAG, "Password reset successfully, auto-unlocking note");
Toast.makeText(NoteEditActivity.this, "密码重置成功", Toast.LENGTH_SHORT).show();
// 自动解锁并显示便签
mPasswordVerified = true;
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
initNoteScreen();
loadAttachments();
} else {
Log.e(TAG, "Failed to set new password");
Toast.makeText(NoteEditActivity.this, "密码设置失败", Toast.LENGTH_SHORT).show();
finish();
}
}
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User cancelled reset password dialog");
finish();
}
});
AlertDialog dialog = builder.create();
dialog.setCancelable(false);
dialog.show();
}
@Override
protected void onResume() {
super.onResume();
// 只有当mWorkingNote已经初始化完成时才初始化界面
if (mWorkingNote != null) {
// 只有当mWorkingNote已经初始化完成且密码已验证(或未加锁)时才初始化界面
if (mWorkingNote != null && mPasswordVerified) {
initNoteScreen(); // 初始化笔记界面(已包含老年人模式应用)
}
}
@ -537,8 +700,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
/**
* IDID
* ID
* 便
*/
if (!mWorkingNote.existInDatabase()) {
if (mPasswordVerified && !mWorkingNote.existInDatabase()) {
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 保存笔记ID
@ -635,13 +799,70 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
// 应用老年人模式
ElderModeUtils.applyElderMode(this, findViewById(android.R.id.content));
// 初始化格式化工具栏按钮
initFormatToolbar();
}
/**
*
*/
private void initFormatToolbar() {
// 字体大小按钮
View btnFontSize = findViewById(R.id.btn_font_size);
if (btnFontSize != null) {
btnFontSize.setOnClickListener(v -> {
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
});
}
// 粗体按钮
View btnBold = findViewById(R.id.btn_bold);
if (btnBold != null) {
btnBold.setOnClickListener(v -> applyRichTextStyle(android.graphics.Typeface.BOLD));
}
// 斜体按钮
View btnItalic = findViewById(R.id.btn_italic);
if (btnItalic != null) {
btnItalic.setOnClickListener(v -> applyRichTextStyle(android.graphics.Typeface.ITALIC));
}
// 下划线按钮
View btnUnderline = findViewById(R.id.btn_underline);
if (btnUnderline != null) {
btnUnderline.setOnClickListener(v -> applyUnderlineStyle());
}
// 删除线按钮
View btnStrikethrough = findViewById(R.id.btn_strikethrough);
if (btnStrikethrough != null) {
btnStrikethrough.setOnClickListener(v -> applyStrikethroughStyle());
}
// 列表模式按钮
View btnListMode = findViewById(R.id.btn_list_mode);
if (btnListMode != null) {
btnListMode.setOnClickListener(v -> {
// 切换列表模式/普通模式
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
});
}
// 插入图片按钮
View btnInsertImage = findViewById(R.id.btn_insert_image);
if (btnInsertImage != null) {
btnInsertImage.setOnClickListener(v -> handleInsertImage());
}
}
@Override
protected void onPause() {
super.onPause();
// 保存笔记
if(saveNote()) {
// 只有在密码已验证通过时才保存笔记,防止加锁便签在密码错误时内容被覆盖
if (mPasswordVerified && saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState(); // 清除设置状态
@ -782,12 +1003,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
getMenuInflater().inflate(R.menu.note_edit, menu); // 普通笔记菜单
}
// 设置列表模式/普通模式菜单项标题
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
// 列表模式已移至工具栏,不再在菜单中设置标题
// 根据是否有闹钟提醒显示/隐藏相关菜单项
if (mWorkingNote.hasClockAlert()) {
@ -825,18 +1041,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE); // 显示字体大小选择器
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
// 切换列表模式/普通模式
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_insert_image:
handleInsertImage(); // 插入图片
break;
// 字体大小、列表模式、插入图片已移至工具栏
case R.id.menu_share:
getWorkingText(); // 获取工作文本
sendTo(this, mWorkingNote.getContent()); // 分享笔记
@ -850,18 +1055,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false); // 删除提醒
break;
case R.id.menu_bold:
applyRichTextStyle(android.graphics.Typeface.BOLD);
break;
case R.id.menu_italic:
applyRichTextStyle(android.graphics.Typeface.ITALIC);
break;
case R.id.menu_underline:
applyUnderlineStyle();
break;
case R.id.menu_strikethrough:
applyStrikethroughStyle();
break;
// 粗体、斜体、下划线、删除线、字体大小已移至工具栏
default:
break;
}
@ -919,17 +1113,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
Log.d(TAG, "Wrong note id, should not happen");
}
// 根据是否启用同步模式执行不同的删除操作
if (!isSyncMode()) {
// 普通模式:直接删除
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
// 同步模式:移动到回收站
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
// 将笔记移动到回收站,而不是直接删除
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
mWorkingNote.markDeleted(true); // 标记为已删除

@ -70,6 +70,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import net.micode.notes.cloud.SyncManager;
import net.micode.notes.cloud.SyncDialogManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
@ -117,7 +118,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
private enum ListEditState {
NOTE_LIST, // 普通笔记列表状态
SUB_FOLDER, // 子文件夹浏览状态
CALL_RECORD_FOLDER // 通话记录文件夹浏览状态
CALL_RECORD_FOLDER, // 通话记录文件夹浏览状态
SEARCH // 搜索状态
};
// 当前列表状态
@ -153,6 +155,11 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
// 下拉刷新布局
private SwipeRefreshLayout mSwipeRefreshLayout;
// 搜索相关变量
private String mSearchKey = ""; // 搜索关键词
private View mSearchContainer; // 搜索容器视图
private EditText mSearchEditText; // 搜索输入框
// 普通查询条件(非根文件夹)
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
@ -206,19 +213,35 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
if (itemData == null || itemData.getType() != Notes.TYPE_NOTE) {
return;
}
// 查询笔记的原始父文件夹ID
long originParentId = Notes.ID_ROOT_FOLDER; // 默认恢复到根文件夹
Cursor cursor = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, itemData.getId()),
new String[]{NoteColumns.ORIGIN_PARENT_ID}, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
originParentId = cursor.getLong(0);
// 如果原始文件夹ID无效或者是回收站则恢复到根文件夹
if (originParentId <= 0 || originParentId == Notes.ID_TRASH_FOLDER) {
originParentId = Notes.ID_ROOT_FOLDER;
}
}
cursor.close();
}
// 显示确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("恢复笔记");
builder.setMessage("确定要恢复这条笔记吗?");
builder.setIcon(android.R.drawable.ic_dialog_info);
final long targetFolderId = originParentId;
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 恢复笔记到根文件夹
// 恢复笔记到原始文件夹
HashSet<Long> ids = new HashSet<Long>();
ids.add(itemData.getId());
if (DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_ROOT_FOLDER)) {
if (DataUtils.batchMoveToFolder(mContentResolver, ids, targetFolderId)) {
Toast.makeText(NotesListActivity.this, "笔记已恢复", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NotesListActivity.this, "恢复失败", Toast.LENGTH_SHORT).show();
@ -330,8 +353,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// 下拉刷新时触发同步
triggerSync();
// 下拉刷新时只执行下载操作
executeDownload();
}
});
// 设置刷新颜色
@ -575,10 +598,11 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
// 根据当前文件夹选择查询条件
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
// 修改排序规则先按类型降序系统文件夹在前再按alerted_date降序置顶便签在前最后按修改时间降序
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}, NoteColumns.TYPE + " DESC," + NoteColumns.ALERTED_DATE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
/**
@ -654,10 +678,19 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
// 获取选中笔记的小部件属性
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
// 无论是否同步模式,都将笔记移动到回收站
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
HashSet<Long> selectedIds = mNotesListAdapter.getSelectedItemIds();
// 根据当前文件夹决定删除方式
if (mCurrentFolderId == Notes.ID_TRASH_FOLDER) {
// 在回收站中,彻底删除笔记
if (!DataUtils.batchDeleteNotes(mContentResolver, selectedIds)) {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// 不在回收站中,将笔记移动到回收站
if (!DataUtils.batchMoveToFolder(mContentResolver, selectedIds, Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
return widgets;
}
@ -683,25 +716,51 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
*/
private void batchPinNotes() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
private int pinnedCount = 0;
private int unpinnedCount = 0;
@Override
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
// 获取选中笔记的小部件属性
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
HashSet<Long> selectedIds = mNotesListAdapter.getSelectedItemIds();
// 批量更新笔记的alerted_date字段为一个很大的值,表示置顶
// 批量更新笔记的alerted_date字段
for (Long noteId : selectedIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.ALERTED_DATE, 9999999999999L); // 很大的值,表示置顶
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
try {
// 查询笔记当前的alerted_date值
Cursor cursor = getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
new String[]{NoteColumns.ALERTED_DATE},
null, null, null);
long currentAlertedDate = 0;
if (cursor != null && cursor.moveToFirst()) {
currentAlertedDate = cursor.getLong(0);
cursor.close();
}
ContentValues values = new ContentValues();
// 判断是否已经置顶alerted_date大于当前时间
if (currentAlertedDate > System.currentTimeMillis()) {
// 已经置顶,取消置顶
values.put(NoteColumns.ALERTED_DATE, 0); // 0表示未置顶
unpinnedCount++;
} else {
// 未置顶,设置置顶
values.put(NoteColumns.ALERTED_DATE, 9999999999999L); // 很大的值,表示置顶
pinnedCount++;
}
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
values, null, null);
} catch (Exception e) {
Log.e(TAG, "Error pinning note: " + noteId, e);
Log.e(TAG, "Error toggling pin status: " + noteId, e);
}
}
return widgets;
@ -718,7 +777,16 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
}
}
}
Toast.makeText(NotesListActivity.this, "已置顶" + mNotesListAdapter.getSelectedCount() + "条笔记", Toast.LENGTH_SHORT).show();
// 显示相应的提示信息
if (pinnedCount > 0 && unpinnedCount > 0) {
Toast.makeText(NotesListActivity.this, "已置顶" + pinnedCount + "条笔记,已取消置顶" + unpinnedCount + "条笔记", Toast.LENGTH_SHORT).show();
} else if (pinnedCount > 0) {
Toast.makeText(NotesListActivity.this, "已置顶" + pinnedCount + "条笔记", Toast.LENGTH_SHORT).show();
} else if (unpinnedCount > 0) {
Toast.makeText(NotesListActivity.this, "已取消置顶" + unpinnedCount + "条笔记", Toast.LENGTH_SHORT).show();
}
mModeCallBack.finishActionMode(); // 结束多选模式
}
}.execute();
@ -927,6 +995,9 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
public void onBackPressed() {
// 处理返回键,根据当前状态返回上一级或退出
switch (mState) {
case SEARCH:
exitSearch();
break;
case SUB_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
@ -1079,6 +1150,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else if (mState == ListEditState.SEARCH) {
// 搜索状态下不显示菜单,因为搜索框已经占据了工具栏
} else {
Log.e(TAG, "Wrong state:" + mState);
}
@ -1115,10 +1188,128 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false);
toggleToSearch();
return true;
}
/**
*
*
*/
private void showSearch() {
if (mState != ListEditState.SEARCH) {
// 切换到搜索状态
mState = ListEditState.SEARCH;
// 隐藏新建笔记按钮
mAddNewNote.setVisibility(View.GONE);
// 隐藏标题栏,显示搜索容器
mTitleBar.setVisibility(View.GONE);
mSearchContainer = findViewById(R.id.search_container);
mSearchContainer.setVisibility(View.VISIBLE);
mSearchEditText = (EditText) mSearchContainer.findViewById(R.id.search_input);
// 设置搜索输入框监听器
mSearchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
mSearchKey = s.toString();
// 根据搜索关键字刷新列表
startSearchQuery();
}
});
// 监听搜索框软键盘的"完成"键
mSearchEditText.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH) {
mSearchKey = mSearchEditText.getText().toString();
startSearchQuery();
return true;
}
return false;
});
mSearchEditText.requestFocus();
showSoftInput();
// 设置取消按钮点击监听器
Button cancelButton = (Button) mSearchContainer.findViewById(R.id.search_cancel);
cancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
exitSearch();
}
});
}
}
/**
*
*/
private void startSearchQuery() {
// 只搜索SNIPPET列因为CONTENT列在DataColumns中
String selection = NoteColumns.SNIPPET + " LIKE ?";
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection,
new String[] {"%" + mSearchKey + "%"},
NoteColumns.MODIFIED_DATE + " DESC");
}
/**
*
* showSearch
*
*/
private void toggleToSearch() {
if (mState != ListEditState.SEARCH) {
// 如果当前不在根目录,先切换到根目录
if (mCurrentFolderId != Notes.ID_ROOT_FOLDER) {
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
startAsyncNotesListQuery();
}
showSearch();
} else {
if (!mSearchKey.equals(mSearchEditText.getText().toString())) {
startSearchQuery();
}
}
}
/**
* 退
*/
private void exitSearch() {
if (mState == ListEditState.SEARCH) {
// 切换回普通笔记列表状态
mState = ListEditState.NOTE_LIST;
// 清空搜索关键字
mSearchKey = "";
// 隐藏搜索容器,显示标题栏
if (mSearchContainer != null) {
mSearchContainer.setVisibility(View.GONE);
}
mTitleBar.setVisibility(View.VISIBLE);
mTitleBar.setText(R.string.app_name);
// 显示新建笔记按钮
mAddNewNote.setVisibility(View.VISIBLE);
// 隐藏软键盘
if (mSearchEditText != null) {
hideSoftInput(mSearchEditText);
}
// 刷新笔记列表
startAsyncNotesListQuery();
// 重新创建菜单,确保设置图标显示
invalidateOptionsMenu();
}
}
/**
*
*/
@ -1228,6 +1419,13 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
Log.e(TAG, "Wrong note type in CALL_RECORD_FOLDER");
}
break;
case SEARCH:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in SEARCH");
}
break;
default:
break;
}
@ -1264,13 +1462,45 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
*
*/
private void triggerSync() {
SyncManager.getInstance(this).sync(new SyncManager.SyncCallback() {
SyncDialogManager.showSyncOptionsDialog(this, new SyncDialogManager.SyncOptionListener() {
@Override
public void onUploadSelected() {
SyncDialogManager.showUploadConfirmDialog(NotesListActivity.this, new SyncDialogManager.ConfirmListener() {
@Override
public void onConfirm() {
executeUpload();
}
@Override
public void onCancel() {
// 取消操作,返回选择对话框
}
});
}
@Override
public void onDownloadSelected() {
executeDownload();
}
@Override
public void onCanceled() {
// 取消操作
}
});
}
/**
*
*/
private void executeUpload() {
SyncManager.getInstance(this).triggerUpload(new SyncManager.SyncCallback() {
@Override
public void onSyncStart() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, "正在同步...", Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, "正在上传...", Toast.LENGTH_SHORT).show();
// 开始刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(true);
@ -1284,7 +1514,58 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, "同步成功", Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, "上传成功", Toast.LENGTH_SHORT).show();
// 停止刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
// 刷新笔记列表
startAsyncNotesListQuery();
}
});
}
@Override
public void onSyncFailed(final String errorMessage) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
// 停止刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
}
});
}
});
}
/**
*
*/
private void executeDownload() {
SyncManager.getInstance(this).triggerDownload(new SyncManager.SyncCallback() {
@Override
public void onSyncStart() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, "正在下载...", Toast.LENGTH_SHORT).show();
// 开始刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(true);
}
}
});
}
@Override
public void onSyncSuccess() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, "下载成功", Toast.LENGTH_SHORT).show();
// 停止刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
@ -1618,9 +1899,12 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
/**
*
*
* @param folderId ID
*/
private void showFolderPasswordDialog(final long folderId) {
Log.d(TAG, "Showing folder password dialog for folderId: " + folderId);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("输入密码");
builder.setIcon(android.R.drawable.ic_lock_idle_lock);
@ -1635,11 +1919,15 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordInput.getText().toString();
String password = passwordInput.getText().toString().trim();
Log.d(TAG, "User entered password for folder: " + folderId);
if (checkFolderPassword(folderId, password)) {
Log.i(TAG, "Folder password verified successfully, entering folder: " + folderId);
// 密码正确,进入文件夹
enterEncryptedFolder(folderId);
} else {
Log.w(TAG, "Folder password verification failed for folder: " + folderId);
// 密码错误,显示提示
Toast.makeText(NotesListActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
// 重新显示密码输入对话框
@ -1651,23 +1939,37 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
// 设置取消按钮
builder.setNegativeButton("取消", null);
// 添加"忘记密码"按钮,点击后通过密保问题验证重置密码
builder.setNeutralButton(R.string.btn_forgot_password, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User clicked forgot password button for folder: " + folderId);
// 显示通过密保问题重置密码的对话框
showResetPasswordWithSecurityQuestionsDialog(folderId);
}
});
// 显示对话框
builder.show();
}
/**
*
*
* @param folderId ID
*/
private void showResetPasswordWithSecurityQuestionsDialog(final long folderId) {
Log.d(TAG, "Showing reset password with security questions dialog for folder: " + folderId);
// 检查是否设置了密保问题
if (!NotesPreferenceActivity.hasSecurityQuestionsSet(this)) {
Log.w(TAG, "Security questions not set, cannot reset folder password");
Toast.makeText(this, "请先在设置中设置密保问题", Toast.LENGTH_SHORT).show();
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("忘记密码");
builder.setTitle(R.string.title_forgot_password);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage("请回答密保问题以重置密码");
@ -1689,15 +1991,20 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
builder.setView(layout);
// 设置确定按钮
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = nameInput.getText().toString();
String birthday = birthdayInput.getText().toString();
String name = nameInput.getText().toString().trim();
String birthday = birthdayInput.getText().toString().trim();
Log.d(TAG, "Verifying security questions for folder password reset - name: " + name);
if (NotesPreferenceActivity.verifySecurityQuestions(NotesListActivity.this, name, birthday)) {
Log.i(TAG, "Security questions verified successfully for folder: " + folderId);
// 验证成功,显示设置新密码的对话框
showSetNewFolderPasswordDialog(folderId);
} else {
Log.w(TAG, "Security questions verification failed for folder password reset");
// 验证失败,显示提示
Toast.makeText(NotesListActivity.this, "密保问题回答错误", Toast.LENGTH_SHORT).show();
}
@ -1705,7 +2012,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
});
// 设置取消按钮
builder.setNegativeButton("取消", null);
builder.setNegativeButton(android.R.string.cancel, null);
// 显示对话框
builder.show();
@ -1713,25 +2020,35 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
/**
*
*
* @param folderId ID
*/
private void showSetNewFolderPasswordDialog(final long folderId) {
Log.d(TAG, "Showing set new folder password dialog for folder: " + folderId);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("设置新密码");
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setMessage("验证成功,请设置新密码");
// 创建密码输入框
final EditText passwordInput = new EditText(this);
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordInput.setHint("请输入新密码");
builder.setView(passwordInput);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
layout.addView(passwordInput);
builder.setView(layout);
// 设置确定按钮
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String newPassword = passwordInput.getText().toString();
String newPassword = passwordInput.getText().toString().trim();
if (!newPassword.isEmpty()) {
Log.i(TAG, "Setting new password for folder: " + folderId);
// 保存新密码
SharedPreferences preferences = getSharedPreferences("notes_prefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
@ -1739,8 +2056,10 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
editor.apply();
// 显示提示
Toast.makeText(NotesListActivity.this, "密码重置成功", Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, "密码重置成功,请使用新密码进入文件夹", Toast.LENGTH_SHORT).show();
Log.i(TAG, "Folder password reset successfully for folder: " + folderId);
} else {
Log.w(TAG, "New folder password is empty");
// 密码为空,显示提示
Toast.makeText(NotesListActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
}
@ -1748,7 +2067,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
});
// 设置取消按钮
builder.setNegativeButton("取消", null);
builder.setNegativeButton(android.R.string.cancel, null);
// 显示对话框
builder.show();

Loading…
Cancel
Save