diff --git a/src/data/Notes.java b/src/data/Notes.java
index 6273380..8142bef 100644
--- a/src/data/Notes.java
+++ b/src/data/Notes.java
@@ -17,40 +17,24 @@
package net.micode.notes.data;
import android.net.Uri;
-
-/**
- *
- * @Package: net.micode.notes.data
- * @ClassName: Notes
- * @Description: 集中定义一些常量
- */
public class Notes {
- //定义此应用ContentProvider的唯一标识
- public static final String AUTHORITY = "net.micode.notes.provider";
+ public static final String AUTHORITY = "micode_notes";
public static final String TAG = "Notes";
-
- /**
- *{@link Notes#TYPE_NOTE } 普通便签
- *{@link Notes#TYPE_FOLDER }文件夹
- *{@link Notes#TYPE_SYSTEM } 系统
- */
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
/**
* Following IDs are system folders' identifiers
- * {@link Notes#ID_ROOT_FOLDER } is default folder 默认文件夹
- * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder 不属于文件夹的便签
- * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records 存储通话记录
- * {@link Notes#ID_TRASH_FOLER} 垃圾文件夹
+ * {@link Notes#ID_ROOT_FOLDER } is default folder
+ * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
+ * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
- //定义Intent Extra 的常量,用于在不同组件之间安全地传递数据
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";
@@ -62,10 +46,10 @@ public class Notes {
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
- // 将不同数据类型对应到其MIMEitem类型字符串
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
+ public static final String ENCRYPTED_FOLDER = "vnd.android.cursor.item/encrypted_folder";
}
/**
@@ -78,7 +62,6 @@ public class Notes {
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
- //Note表数据库列名常量接口
public interface NoteColumns {
/**
* The unique ID for a row
@@ -113,8 +96,6 @@ public class Notes {
/**
* Folder's name or text content of note
- * 如果是文件夹,存储文件夹名字
- * 如果是便签,存储便签摘要
*
Type: TEXT
*/
public static final String SNIPPET = "snippet";
@@ -187,7 +168,6 @@ public class Notes {
public static final String VERSION = "version";
}
- //Data表数据库列名常量接口
public interface DataColumns {
/**
* The unique ID for a row
@@ -262,12 +242,10 @@ public class Notes {
public static final String DATA5 = "data5";
}
- //DataColumns接口的实现方式1:文本便签的数据模型
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* Type: Integer 1:check list mode 0: normal mode
- * data1表示是否是清单模式
*/
public static final String MODE = DATA1;
@@ -280,19 +258,16 @@ public class Notes {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
- //DataColumns接口的实现方式2:通话便签的数据模型
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* Type: INTEGER (long)
- * data1表示通话时间戳
*/
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* Type: TEXT
- * data3表示电话号码
*/
public static final String PHONE_NUMBER = DATA3;
diff --git a/src/tool/DataUtils.java b/src/tool/DataUtils.java
index 3c3e580..93157d8 100644
--- a/src/tool/DataUtils.java
+++ b/src/tool/DataUtils.java
@@ -37,7 +37,6 @@ import java.util.HashSet;
public class DataUtils {
public static final String TAG = "DataUtils";
- //批量操作删除便签,发生错误就返回flase,删除成功就返回true
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
@@ -47,42 +46,41 @@ public class DataUtils {
Log.d(TAG, "no id is in the hashset");
return true;
}
- // 构建删除操作:拼接标签ID到标签ContentUR
+
ArrayList operationList = new ArrayList();
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
-
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
- //创建一个 ContentProviderOperation.Builder,删除指定id的标签 只查询 ID 为 id 的那一条记录
operationList.add(builder.build());
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
- if (results == null || results.length == 0 || results[0] == null) {////上面的一条执行数据库操作删除并返回结果数组
+ if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
- } catch (RemoteException e) { //记录错误信息
+ } catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
- //便签转进新文件夹
+
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
- //批量操作便签转进新文件夹
+
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids,
long folderId) {
if (ids == null) {
@@ -93,9 +91,10 @@ public class DataUtils {
ArrayList operationList = new ArrayList();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
- .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); //创建更新,
+ .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
+ builder.withValue(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
operationList.add(builder.build());
}
@@ -113,21 +112,11 @@ public class DataUtils {
}
return false;
}
+
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
- public static int getUserFolderCount(ContentResolver resolver) {
- //查询设备中「类型为文件夹(TYPE_FOLDER)」且「父文件夹 ID 不等于回收站 ID(ID_TRASH_FOLER)」的文件夹总数。
-'''
-触发了一次跨进程的数据库查询操作
-Cursor query(
- Uri uri, // 要查询的数据 URI
- String[] projection, // 要返回的列(字段),null 表示所有列
- String selection, // WHERE 子句(不含 "WHERE" 关键字)
- String[] selectionArgs, // WHERE 子句中占位符 ? 的实际值
- String sortOrder // 排序方式(如 "date DESC")
-);
-'''
+ public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
@@ -138,7 +127,7 @@ Cursor query(
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
- count = cursor.getInt(0);//从当前游标指向的行中,读取第 0 列(第一列)的整数类型数据
+ count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
@@ -148,11 +137,11 @@ Cursor query(
}
return count;
}
- //变迁是否可见(没进回收站)
+
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
- NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, //笔记的 parent_id 不能等于回收站的 ID(即不在回收站中)
+ NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
@@ -165,7 +154,7 @@ Cursor query(
}
return exist;
}
- //便签还存在否
+
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
@@ -179,7 +168,7 @@ Cursor query(
}
return exist;
}
- //便签数据还存在否
+
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
@@ -193,12 +182,12 @@ Cursor query(
}
return exist;
}
- //查看是否存在一个名字是XX的文件夹
+
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
- NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + //类型是文件夹
- " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +//没进回收站
- " AND " + NoteColumns.SNIPPET + "=?", //存储在snippet的名字和下面的name一样
+ NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
+ " AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
@@ -209,9 +198,9 @@ Cursor query(
}
return exist;
}
- //获得文件夹下的便签,并仅返回它们的 Widget ID 和 Widget 类型
+
public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) {
- Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, //获得便签
+ Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
@@ -236,7 +225,7 @@ Cursor query(
}
return set;
}
- //获得电话号
+
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
@@ -246,7 +235,7 @@ Cursor query(
if (cursor != null && cursor.moveToFirst()) {
try {
- return cursor.getString(0); //返回电话号
+ return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
@@ -255,7 +244,7 @@ Cursor query(
}
return "";
}
-//通过电话号和日期查找便签
+
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
@@ -276,7 +265,7 @@ Cursor query(
}
return 0;
}
-//通过id获得名字
+
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
@@ -294,15 +283,89 @@ Cursor query(
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
-//从一段文本(snippet)中提取作为标题
+
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
- snippet = snippet.trim();//清空无用制表符
- int index = snippet.indexOf('\n');//index即录字符串中第一个换行符 \n 出现的位置
+ snippet = snippet.trim();
+ int index = snippet.indexOf('\n');
if (index != -1) {
- snippet = snippet.substring(0, index);//只获取换行前的部分(核心)。
+ snippet = snippet.substring(0, index);
}
}
return snippet;
}
+
+ /**
+ * 获取指定文件夹中的所有便签ID
+ * @param resolver ContentResolver实例
+ * @param folderId 文件夹ID
+ * @return 便签ID的HashSet
+ */
+ public static HashSet getNotesInFolder(ContentResolver resolver, long folderId) {
+ Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
+ new String[] { NoteColumns.ID },
+ NoteColumns.PARENT_ID + "=?",
+ new String[] { String.valueOf(folderId) },
+ null);
+
+ HashSet ids = new HashSet();
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ do {
+ try {
+ ids.add(cursor.getLong(0));
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, "Get note id fails " + e.toString());
+ }
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return ids;
+ }
+ public static boolean batchMoveToTrash(ContentResolver resolver, HashSet ids,
+ long originFolderId) {
+ if (ids == null) {
+ Log.d(TAG, "the ids is null");
+ return true;
+ }
+ ArrayList operationList = new ArrayList();
+ long now = System.currentTimeMillis();
+ for (long id : ids) {
+ ContentProviderOperation.Builder builder = ContentProviderOperation
+ .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
+ builder.withValue(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
+ builder.withValue(NoteColumns.ORIGIN_PARENT_ID, originFolderId);
+ builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
+ builder.withValue(NoteColumns.MODIFIED_DATE, now);
+ operationList.add(builder.build());
+ }
+ try {
+ ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
+ if (results == null || results.length == 0 || results[0] == null) {
+ Log.d(TAG, "move to trash failed, ids:" + ids.toString());
+ return false;
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ }
+ return false;
+ }
+
+ public static void moveNotesToTrashForFolder(ContentResolver resolver, long folderId) {
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
+ values.put(NoteColumns.ORIGIN_PARENT_ID, folderId);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
+ resolver.update(Notes.CONTENT_NOTE_URI, values,
+ NoteColumns.PARENT_ID + "=?",
+ new String[] { String.valueOf(folderId) });
+ }
+
}
+
+
diff --git a/src/ui/EncryptedFolderManager.java b/src/ui/EncryptedFolderManager.java
new file mode 100644
index 0000000..326177c
--- /dev/null
+++ b/src/ui/EncryptedFolderManager.java
@@ -0,0 +1,218 @@
+
+ /*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.micode.notes.ui;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.tool.DataUtils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class EncryptedFolderManager {
+ private static final String TAG = "EncryptedFolderManager";
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final Callback mCallback;
+
+ public interface Callback {
+ void onEncryptedFolderCreated();
+
+ void onEncryptedFolderUnlocked(NoteItemData data);
+ }
+
+ public EncryptedFolderManager(Context context, ContentResolver resolver, Callback callback) {
+ mContext = context;
+ mResolver = resolver;
+ mCallback = callback;
+ }
+
+ public void showCreateEncryptedFolderDialog() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_encrypted_folder, null);
+ final EditText etName = (EditText) view.findViewById(R.id.et_encrypted_folder_name);
+ final EditText etQuestion = (EditText) view.findViewById(R.id.et_encrypted_question);
+ final EditText etAnswer = (EditText) view.findViewById(R.id.et_encrypted_answer);
+ etAnswer.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ builder.setTitle(R.string.encrypted_folder_title);
+ builder.setView(view);
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ final Dialog dialog = builder.show();
+ final Button positive = (Button) dialog.findViewById(android.R.id.button1);
+ positive.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String name = etName.getText().toString().trim();
+ String question = etQuestion.getText().toString().trim();
+ String answer = etAnswer.getText().toString().trim();
+ if (TextUtils.isEmpty(name)) {
+ etName.setError(mContext.getString(R.string.hint_foler_name));
+ return;
+ }
+ if (TextUtils.isEmpty(question)) {
+ etQuestion.setError(mContext.getString(R.string.encrypted_question_empty));
+ return;
+ }
+ if (TextUtils.isEmpty(answer)) {
+ etAnswer.setError(mContext.getString(R.string.encrypted_answer_empty));
+ return;
+ }
+ if (DataUtils.checkVisibleFolderName(mResolver, name)) {
+ Toast.makeText(mContext,
+ mContext.getString(R.string.folder_exist, name), Toast.LENGTH_LONG).show();
+ return;
+ }
+ long folderId = createEncryptedFolder(name, question, answer);
+ if (folderId > 0) {
+ dialog.dismiss();
+ if (mCallback != null) {
+ mCallback.onEncryptedFolderCreated();
+ }
+ }
+ }
+ });
+ }
+
+ public EncryptedFolderInfo getEncryptedFolderInfo(long folderId) {
+ Cursor cursor = mResolver.query(Notes.CONTENT_DATA_URI,
+ new String[] { DataColumns.DATA3, DataColumns.DATA4 },
+ DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?",
+ new String[] { String.valueOf(folderId), Notes.DataConstants.ENCRYPTED_FOLDER },
+ null);
+ if (cursor == null) {
+ return null;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ String question = cursor.getString(0);
+ String answerHash = cursor.getString(1);
+ if (!TextUtils.isEmpty(question) && !TextUtils.isEmpty(answerHash)) {
+ return new EncryptedFolderInfo(folderId, question, answerHash);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ return null;
+ }
+
+ public void showEncryptedUnlockDialog(final EncryptedFolderInfo info, final NoteItemData data) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_encrypted_unlock, null);
+ TextView tvQuestion = (TextView) view.findViewById(R.id.tv_encrypted_question);
+ final EditText etAnswer = (EditText) view.findViewById(R.id.et_encrypted_answer);
+ tvQuestion.setText(info.question);
+ builder.setTitle(R.string.encrypted_unlock_title);
+ builder.setView(view);
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ final Dialog dialog = builder.show();
+ final Button positive = (Button) dialog.findViewById(android.R.id.button1);
+ positive.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String answer = etAnswer.getText().toString().trim();
+ if (TextUtils.isEmpty(answer)) {
+ etAnswer.setError(mContext.getString(R.string.encrypted_answer_empty));
+ return;
+ }
+ if (!TextUtils.equals(hashAnswer(answer), info.answerHash)) {
+ Toast.makeText(mContext, R.string.encrypted_answer_wrong,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ dialog.dismiss();
+ if (mCallback != null) {
+ mCallback.onEncryptedFolderUnlocked(data);
+ }
+ }
+ });
+ }
+
+ private long createEncryptedFolder(String name, String question, String answer) {
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.SNIPPET, name);
+ values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ Uri uri = mResolver.insert(Notes.CONTENT_NOTE_URI, values);
+ if (uri == null) {
+ return -1;
+ }
+ long folderId = -1;
+ try {
+ folderId = Long.parseLong(uri.getPathSegments().get(1));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Create encrypted folder failed", e);
+ return -1;
+ }
+ ContentValues dataValues = new ContentValues();
+ dataValues.put(DataColumns.NOTE_ID, folderId);
+ dataValues.put(DataColumns.MIME_TYPE, Notes.DataConstants.ENCRYPTED_FOLDER);
+ dataValues.put(DataColumns.DATA3, question);
+ dataValues.put(DataColumns.DATA4, hashAnswer(answer));
+ mResolver.insert(Notes.CONTENT_DATA_URI, dataValues);
+ return folderId;
+ }
+
+ private String hashAnswer(String answer) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] result = digest.digest(answer.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (byte b : result) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, "Hash error", e);
+ return "";
+ }
+ }
+
+ public static class EncryptedFolderInfo {
+ private final long folderId;
+ private final String question;
+ private final String answerHash;
+
+ private EncryptedFolderInfo(long folderId, String question, String answerHash) {
+ this.folderId = folderId;
+ this.question = question;
+ this.answerHash = answerHash;
+ }
+ }
+}
\ No newline at end of file