diff --git a/src/notes/data/Notes.java b/src/notes/data/Notes.java
index ef9a4d5..15f4606 100644
--- a/src/notes/data/Notes.java
+++ b/src/notes/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,7 +46,6 @@ 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;
@@ -79,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
@@ -114,8 +96,6 @@ public class Notes {
/**
* Folder's name or text content of note
- * 如果是文件夹,存储文件夹名字
- * 如果是便签,存储便签摘要
*
Type: TEXT
*/
public static final String SNIPPET = "snippet";
@@ -199,7 +179,6 @@ public class Notes {
public static final String HABIT_CONFIG = "habit_config";
}
- //Data表数据库列名常量接口
public interface DataColumns {
/**
* The unique ID for a row
@@ -274,12 +253,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;
@@ -292,19 +269,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/notes/data/NotesDatabaseHelper.java b/src/notes/data/NotesDatabaseHelper.java
index 12b1b3e..e1e055f 100644
--- a/src/notes/data/NotesDatabaseHelper.java
+++ b/src/notes/data/NotesDatabaseHelper.java
@@ -35,7 +35,7 @@ import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db";
- private static final int DB_VERSION = 5;
+ private static final int DB_VERSION = 4;
public interface TABLE {
public static final String NOTE = "note";
@@ -68,9 +68,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.IS_HABIT + " INTEGER NOT NULL DEFAULT 0," +
- NoteColumns.HABIT_CONFIG + " TEXT NOT NULL DEFAULT ''" +
+ NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
/**创建data表的sql语句
@@ -406,11 +404,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
- if (oldVersion == 4) {
- upgradeToV5(db);
- oldVersion++;
- }
-
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
@@ -451,12 +444,4 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
-
- //更新到版本5:增加习惯打卡支持字段
- private void upgradeToV5(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.IS_HABIT
- + " INTEGER NOT NULL DEFAULT 0");
- db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.HABIT_CONFIG
- + " TEXT NOT NULL DEFAULT ''");
- }
}
diff --git a/src/notes/tool/DataUtils.java b/src/notes/tool/DataUtils.java
index 6244b1b..dc35bfb 100644
--- a/src/notes/tool/DataUtils.java
+++ b/src/notes/tool/DataUtils.java
@@ -323,15 +323,25 @@ public class DataUtils {
}
return ids;
}
+<<<<<<< HEAD:src/notes/tool/DataUtils.java
public static boolean batchMoveToTrash(ContentResolver resolver, HashSet ids, long originFolderId) {
if (ids == null || ids.isEmpty()) {
Log.d(TAG, "the ids is null or empty");
+=======
+ public static boolean batchMoveToTrash(ContentResolver resolver, HashSet ids,
+ long originFolderId) {
+ if (ids == null) {
+ Log.d(TAG, "the ids is null");
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
return true;
}
ArrayList operationList = new ArrayList();
long now = System.currentTimeMillis();
+<<<<<<< HEAD:src/notes/tool/DataUtils.java
// 1. 更新便签的PARENT_ID为回收站ID
+=======
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
@@ -341,10 +351,16 @@ public class DataUtils {
builder.withValue(NoteColumns.MODIFIED_DATE, now);
operationList.add(builder.build());
}
+<<<<<<< HEAD:src/notes/tool/DataUtils.java
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0) {
+=======
+ try {
+ ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
+ if (results == null || results.length == 0 || results[0] == null) {
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
Log.d(TAG, "move to trash failed, ids:" + ids.toString());
return false;
}
@@ -367,6 +383,7 @@ public class DataUtils {
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) });
}
+<<<<<<< HEAD:src/notes/tool/DataUtils.java
/**
* 检查文件夹是否为加密文件夹
@@ -389,6 +406,9 @@ public class DataUtils {
}
return isEncrypted;
}
+=======
+
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
}
diff --git a/src/notes/ui/AlarmAlertActivity.java b/src/notes/ui/AlarmAlertActivity.java
index 68a8fdb..ba117f5 100644
--- a/src/notes/ui/AlarmAlertActivity.java
+++ b/src/notes/ui/AlarmAlertActivity.java
@@ -18,16 +18,11 @@ package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.PendingIntent;
-import android.app.AlarmManager;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
-import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
@@ -35,28 +30,16 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
-import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
-import android.view.View;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import java.util.Calendar;
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
- private static final String TAG = "AlarmAlertActivity";
private long mNoteId;
private String mSnippet;
private static final int SNIPPET_PREW_MAX_LEN = 60;
@@ -92,12 +75,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
mPlayer = new MediaPlayer();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
- int isHabit = intent.getIntExtra("habit_alarm", 0);
- if (isHabit == 1) {
- showHabitDialog(intent);
- } else {
- showActionDialog();
- }
+ showActionDialog();
playAlarmSound();
} else {
finish();
@@ -146,190 +124,11 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
if (isScreenOn()) {
- dialog.setNegativeButton(R.string.notealert_enter, this);
+ dialog.setNegativeButton(R.string.notealert_enter, this); //取消
}
- dialog.show().setOnDismissListener(this);
+ dialog.show().setOnDismissListener(this); //设置关闭监听
}
-
- // Habit specific dialog with actions: complete, snooze, skip, abandon
- private void showHabitDialog(Intent intent) {
- View v = getLayoutInflater().inflate(R.layout.habit_alert_dialog, null);
- TextView tvTitle = (TextView) v.findViewById(R.id.habit_alert_title);
- TextView tvSnippet = (TextView) v.findViewById(R.id.habit_alert_snippet);
- final Button btnComplete = (Button) v.findViewById(R.id.habit_btn_complete);
- final Button btnSnooze10 = (Button) v.findViewById(R.id.habit_btn_snooze10);
- final Button btnSnooze30 = (Button) v.findViewById(R.id.habit_btn_snooze30);
- final Button btnSkip = (Button) v.findViewById(R.id.habit_btn_skip);
- final Button btnAbandon = (Button) v.findViewById(R.id.habit_btn_abandon);
-
- tvTitle.setText(getString(R.string.app_name));
- tvSnippet.setText(mSnippet);
-
- final AlertDialog d = new AlertDialog.Builder(this)
- .setView(v)
- .create();
-
- btnComplete.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- recordHabitHistory(mNoteId, "completed", "");
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_record_complete, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- btnSnooze10.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- scheduleSnooze(mNoteId, 10);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_snoozed, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- btnSnooze30.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- scheduleSnooze(mNoteId, 30);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_snoozed, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- btnSkip.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showSkipReasonDialog();
- // 不要立即关闭对话框,让用户选择原因
- }
- });
-
- btnAbandon.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- abandonHabit(mNoteId);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_abandoned, Toast.LENGTH_SHORT).show();
- d.dismiss();
- }
- });
-
- d.setOnDismissListener(this);
- d.show();
- }
-
- private void showSkipReasonDialog() {
- final String[] reasons = new String[] { getString(R.string.skip_reason_busy),
- getString(R.string.skip_reason_sick), getString(R.string.skip_reason_other) };
- AlertDialog.Builder b = new AlertDialog.Builder(this);
- b.setTitle(R.string.habit_skip_title);
- b.setItems(reasons, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String reason = reasons[which];
- recordHabitHistory(mNoteId, "skipped", reason);
- Toast.makeText(AlarmAlertActivity.this, R.string.habit_record_skipped, Toast.LENGTH_SHORT).show();
- // 选择原因后关闭主对话框
- dialog.dismiss();
- finish();
- }
- });
- b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- b.show();
- }
-
- private void scheduleSnooze(long noteId, int minutes) {
- try {
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
- intent.putExtra("habit_alarm", 1);
- int req = (int) (noteId ^ 0x100000) + minutes; // unique-ish
- PendingIntent pi = PendingIntent.getBroadcast(this, req, intent, 0);
- AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
- long trigger = System.currentTimeMillis() + minutes * 60 * 1000L;
- am.set(AlarmManager.RTC_WAKEUP, trigger, pi);
- } catch (Exception e) {
- Log.e(TAG, "Schedule snooze error", e);
- }
- }
-
- private void abandonHabit(long noteId) {
- try {
- ContentValues values = new ContentValues();
- values.put(Notes.NoteColumns.IS_HABIT, 0);
- values.put(Notes.NoteColumns.HABIT_CONFIG, "");
- getContentResolver().update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), values, null, null);
- // cancel repeating alarm
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
- intent.putExtra("habit_alarm", 1);
- PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
- AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
- am.cancel(pi);
- } catch (Exception e) {
- Log.e(TAG, "Abandon habit error", e);
- }
- }
-
- // Record history into habit_config.history (append object {date,status,reason})
- private void recordHabitHistory(long noteId, String status, String reason) {
- try {
- Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
- Cursor c = getContentResolver().query(uri, new String[]{Notes.NoteColumns.HABIT_CONFIG}, null, null, null);
- String cfg = "";
- if (c != null) {
- if (c.moveToFirst()) cfg = c.getString(0);
- c.close();
- }
- JSONObject jo = cfg != null && cfg.length() > 0 ? new JSONObject(cfg) : new JSONObject();
- JSONArray history = jo.has("history") ? jo.getJSONArray("history") : new JSONArray();
-
- // 获取当前时间戳
- long recordTime = System.currentTimeMillis();
-
- // 创建新记录
- JSONObject newEntry = new JSONObject();
- newEntry.put("date", recordTime);
- newEntry.put("status", status);
- newEntry.put("reason", reason == null ? "" : reason);
-
- // 检查是否已经存在该日期的记录,如果有则替换
- java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault());
- String recordKey = sdf.format(new java.util.Date(recordTime));
- boolean foundRecord = false;
-
- for (int i = 0; i < history.length(); i++) {
- JSONObject entry = history.getJSONObject(i);
- long entryDate = entry.optLong("date", 0);
- String entryKey = sdf.format(new java.util.Date(entryDate));
- if (recordKey.equals(entryKey)) {
- // 替换该日期的记录
- history.put(i, newEntry);
- foundRecord = true;
- break;
- }
- }
-
- // 如果没有该日期的记录,添加新记录
- if (!foundRecord) {
- history.put(newEntry);
- }
-
- jo.put("history", history);
- ContentValues values = new ContentValues();
- values.put(Notes.NoteColumns.HABIT_CONFIG, jo.toString());
- getContentResolver().update(uri, values, null, null);
- // 通知数据变化,以便日历视图刷新
- getContentResolver().notifyChange(uri, null);
- } catch (JSONException e) {
- Log.e(TAG, "Record habit history json error", e);
- }
- }
-
+ //选择negative时执行跳转到笔记 (那可能positive仅关闭弹窗)
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
diff --git a/src/notes/ui/EncryptedFolderManager.java b/src/notes/ui/EncryptedFolderManager.java
new file mode 100644
index 0000000..326177c
--- /dev/null
+++ b/src/notes/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
diff --git a/src/notes/ui/ImageInsertHelper.java b/src/notes/ui/ImageInsertHelper.java
new file mode 100644
index 0000000..cdc2431
--- /dev/null
+++ b/src/notes/ui/ImageInsertHelper.java
@@ -0,0 +1,172 @@
+/*
+ * 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.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import jp.wasabeef.richeditor.RichEditor;
+
+public class ImageInsertHelper {
+ private static final String TAG = "ImageInsertHelper";
+ private final Activity mActivity;
+ private final int mRequestCode;
+
+ public static class Result {
+ public final boolean success;
+ public final String localPath;
+ public final String html;
+
+ private Result(boolean success, String localPath, String html) {
+ this.success = success;
+ this.localPath = localPath;
+ this.html = html;
+ }
+ }
+
+ public ImageInsertHelper(Activity activity, int requestCode) {
+ mActivity = activity;
+ mRequestCode = requestCode;
+ }
+
+ public void startPickImage() {
+ try {
+ Intent intent;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ } else {
+ intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ }
+ intent.setType("image/*");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ }
+ mActivity.startActivityForResult(intent, mRequestCode);
+ } catch (ActivityNotFoundException e) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ try {
+ mActivity.startActivityForResult(intent, mRequestCode);
+ } catch (ActivityNotFoundException ex) {
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "No image picker available", ex);
+ }
+ }
+ }
+
+ public Result handleActivityResult(int requestCode, int resultCode, Intent data, RichEditor editor) {
+ if (requestCode != mRequestCode) {
+ return null;
+ }
+ if (resultCode != Activity.RESULT_OK || data == null) {
+ return new Result(false, null, null);
+ }
+ Uri uri = data.getData();
+ if (uri == null) {
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return new Result(false, null, null);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ final int takeFlags = data.getFlags()
+ & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ try {
+ mActivity.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Persistable uri permission not granted", e);
+ }
+ }
+ String localImagePath = saveImageToLocal(uri);
+ if (TextUtils.isEmpty(localImagePath)) {
+ return new Result(false, null, null);
+ }
+ String newHtml = appendImageHtml(editor, localImagePath);
+ return new Result(true, localImagePath, newHtml);
+ }
+
+ private String appendImageHtml(RichEditor editor, String localImagePath) {
+ String imgHtmlTag = buildImageHtmlTag(localImagePath);
+ String curHtml = normalizeEditorHtml(editor.getHtml());
+ String newHtml = curHtml + imgHtmlTag;
+ editor.setHtml(newHtml);
+ editor.focusEditor();
+ return newHtml;
+ }
+
+ String buildImageHtmlTag(String localImagePath) {
+ String imgUrl = Uri.fromFile(new File(localImagePath)).toString();
+ return "
";
+ }
+
+ private String normalizeEditorHtml(String html) {
+ if (TextUtils.isEmpty(html) || "null".equalsIgnoreCase(html)) {
+ return "";
+ }
+ return html;
+ }
+
+ private String saveImageToLocal(Uri uri) {
+ try {
+ File baseDir = mActivity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ if (baseDir == null) {
+ baseDir = mActivity.getFilesDir();
+ }
+ File appDir = new File(baseDir, "note_images");
+ if (!appDir.exists() && !appDir.mkdirs()) {
+ Log.e(TAG, "Create image directory failed: " + appDir.getAbsolutePath());
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ String fileName = "note_" + System.currentTimeMillis() + ".jpg";
+ File targetFile = new File(appDir, fileName);
+ try (InputStream is = mActivity.getContentResolver().openInputStream(uri);
+ OutputStream os = new FileOutputStream(targetFile)) {
+ if (is == null) {
+ Log.e(TAG, "Open image stream failed: " + uri);
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = is.read(buffer)) > 0) {
+ os.write(buffer, 0, len);
+ }
+ }
+ return targetFile.getAbsolutePath();
+ } catch (Exception e) {
+ Log.e(TAG, "Save image failed", e);
+ Toast.makeText(mActivity, R.string.error_picture_select, Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ }
+}
diff --git a/src/notes/ui/NoteEditText.java b/src/notes/ui/NoteEditText.java
index 1c42721..03cb5f4 100644
--- a/src/notes/ui/NoteEditText.java
+++ b/src/notes/ui/NoteEditText.java
@@ -75,9 +75,13 @@ public class NoteEditText extends EditText {
void onTextChange(int index, boolean hasText);
}
+<<<<<<< HEAD:src/notes/ui/NoteEditText.java
private OnTextViewChangeListener mOnTextViewChangeListener;
//新增选区变化回调接口
private OnSelectionChangeListener mOnSelectionChangeListener;
+=======
+ private OnTextViewChangeListener mOnTextViewChangeListener; // 文本变化监听器实例
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NoteEditText.java
public NoteEditText(Context context) {
super(context, null);
@@ -91,11 +95,14 @@ public class NoteEditText extends EditText {
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
+<<<<<<< HEAD:src/notes/ui/NoteEditText.java
//新增选区变化回调接口设置方法
public void setOnSelectionChangeListener(OnSelectionChangeListener listener) {
mOnSelectionChangeListener = listener;
}
+=======
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NoteEditText.java
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
@@ -223,19 +230,13 @@ public class NoteEditText extends EditText {
}
super.onCreateContextMenu(menu);
}
-
- //新增选区变化回调接口实现
- @Override
- protected void onSelectionChanged(int selStart, int selEnd) {
- super.onSelectionChanged(selStart, selEnd);
- if (mOnSelectionChangeListener != null) {
- mOnSelectionChangeListener.onSelectionChanged(mIndex, selStart, selEnd);
- }
- }
}
+<<<<<<< HEAD:src/notes/ui/NoteEditText.java
// 新增选区变化回调接口
interface OnSelectionChangeListener {
void onSelectionChanged(int index, int selStart, int selEnd);
}
+=======
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NoteEditText.java
diff --git a/src/notes/ui/NotesListAdapter.java b/src/notes/ui/NotesListAdapter.java
index b4d0f1d..8cff954 100644
--- a/src/notes/ui/NotesListAdapter.java
+++ b/src/notes/ui/NotesListAdapter.java
@@ -44,7 +44,10 @@ public class NotesListAdapter extends CursorAdapter {
public int widgetId;
public int widgetType;
};
+<<<<<<< HEAD:src/notes/ui/NotesListAdapter.java
+=======
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListAdapter.java
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap();
diff --git a/src/notes/ui/NotesListItem.java b/src/notes/ui/NotesListItem.java
index ac4b7af..d5ac8c9 100644
--- a/src/notes/ui/NotesListItem.java
+++ b/src/notes/ui/NotesListItem.java
@@ -32,7 +32,10 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
+<<<<<<< HEAD:src/notes/ui/NotesListItem.java
private ImageView mHabit;
+=======
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListItem.java
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
@@ -72,7 +75,11 @@ public class NotesListItem extends LinearLayout {
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.trash_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
+<<<<<<< HEAD:src/notes/ui/NotesListItem.java
mAlert.setImageResource(R.drawable.baseline_restore_from_trash_24);
+=======
+ mAlert.setImageResource(android.R.drawable.ic_menu_delete);
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListItem.java
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
@@ -84,12 +91,22 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
+<<<<<<< HEAD:src/notes/ui/NotesListItem.java
// habit badge
if (data.isHabit()) {
mHabit.setVisibility(View.VISIBLE);
} else {
mHabit.setVisibility(View.GONE);
}
+=======
+ } else if (data.getId() == Notes.ID_TRASH_FOLER) { //为回收站添加图标和显示逻辑
+ mCallName.setVisibility(View.GONE);
+ mAlert.setVisibility(View.VISIBLE);
+ mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
+ mTitle.setText(context.getString(R.string.trash_folder_name)
+ + context.getString(R.string.format_folder_files_count, data.getNotesCount()));
+ mAlert.setImageResource(R.drawable.baseline_restore_from_trash_24);
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesListItem.java
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
diff --git a/src/notes/ui/NotesPreferenceActivity.java b/src/notes/ui/NotesPreferenceActivity.java
index d111389..9964f6d 100644
--- a/src/notes/ui/NotesPreferenceActivity.java
+++ b/src/notes/ui/NotesPreferenceActivity.java
@@ -81,11 +81,15 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
+<<<<<<< HEAD:src/notes/ui/NotesPreferenceActivity.java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(mReceiver, filter);
}
+=======
+ registerReceiver(mReceiver, filter);
+>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/ui/NotesPreferenceActivity.java
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
diff --git a/src/ui/NoteItemData.java b/src/ui/NoteItemData.java
new file mode 100644
index 0000000..7021ee6
--- /dev/null
+++ b/src/ui/NoteItemData.java
@@ -0,0 +1,345 @@
+/*
+ * 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.content.Context;
+import android.database.Cursor;
+import android.text.TextUtils;
+
+import net.micode.notes.data.Contact;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.tool.DataUtils;
+
+
+/**
+ * 笔记列表项的数据模型类,负责从数据库Cursor中读取数据并封装成Java对象
+ * 包含笔记的基本属性、通话记录信息和列表位置状态
+ */
+public class NoteItemData {
+ /**
+ * 数据库查询的列投影,定义需要从数据库读取的字段
+ */
+ static final String [] PROJECTION = new String [] {
+ NoteColumns.ID, // 笔记ID
+ NoteColumns.ALERTED_DATE, // 提醒日期
+ NoteColumns.BG_COLOR_ID, // 背景颜色ID
+ NoteColumns.CREATED_DATE, // 创建日期
+ NoteColumns.HAS_ATTACHMENT, // 是否有附件
+ NoteColumns.MODIFIED_DATE, // 修改日期
+ NoteColumns.NOTES_COUNT, // 文件夹包含的笔记数量
+ NoteColumns.PARENT_ID, // 父文件夹ID
+ NoteColumns.SNIPPET, // 笔记内容摘要
+ NoteColumns.TYPE, // 笔记类型
+ NoteColumns.WIDGET_ID, // 关联的小部件ID
+ NoteColumns.WIDGET_TYPE, // 小部件类型
+ };
+
+ /**
+ * 投影数组的列索引常量,用于从Cursor中快速获取对应字段
+ */
+ private static final int ID_COLUMN = 0; // 笔记ID
+ private static final int ALERTED_DATE_COLUMN = 1; // 提醒日期
+ private static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色ID
+ private static final int CREATED_DATE_COLUMN = 3; // 创建日期
+ private static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件
+ private static final int MODIFIED_DATE_COLUMN = 5; // 修改日期
+ private static final int NOTES_COUNT_COLUMN = 6; // 文件夹包含的笔记数量
+ private static final int PARENT_ID_COLUMN = 7; // 父文件夹ID
+ private static final int SNIPPET_COLUMN = 8; // 笔记内容摘要
+ private static final int TYPE_COLUMN = 9; // 笔记类型
+ private static final int WIDGET_ID_COLUMN = 10; // 小部件ID
+ private static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型
+
+ // 笔记基本属性
+ private long mId; // 笔记唯一标识符
+ private long mAlertDate; // 提醒日期时间
+ private int mBgColorId; // 背景颜色ID
+ private long mCreatedDate; // 创建日期时间
+ private boolean mHasAttachment; // 是否包含附件
+ private long mModifiedDate; // 最后修改日期时间
+ private int mNotesCount; // 文件夹包含的笔记数量
+ private long mParentId; // 所属父文件夹ID
+ private String mSnippet; // 笔记内容摘要
+ private int mType; // 笔记类型(普通笔记/文件夹/系统文件夹)
+ private int mWidgetId; // 关联的小部件ID
+ private int mWidgetType; // 小部件类型
+
+ // 通话记录相关属性
+ private String mName; // 通话记录联系人姓名
+ private String mPhoneNumber; // 通话记录电话号码
+
+ // 列表位置状态
+ private boolean mIsLastItem; // 是否为列表最后一项
+ private boolean mIsFirstItem; // 是否为列表第一项
+ private boolean mIsOnlyOneItem; // 是否为列表唯一一项
+ private boolean mIsOneNoteFollowingFolder; // 是否是文件夹下的唯一笔记
+ private boolean mIsMultiNotesFollowingFolder; // 是否是文件夹下的多个笔记之一
+
+ /**
+ * 构造函数,从Cursor中读取数据并初始化成员变量
+ * @param cursor 包含笔记数据的Cursor
+ */
+ public NoteItemData(Context context, Cursor cursor) {
+ // 读取基本笔记属性
+ mId = cursor.getLong(ID_COLUMN);
+ mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
+ mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
+ mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
+ mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
+ mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
+ mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
+ mParentId = cursor.getLong(PARENT_ID_COLUMN);
+ mSnippet = cursor.getString(SNIPPET_COLUMN);
+ mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
+ NoteEditActivity.TAG_UNCHECKED, "");// 清除清单模式的标签标记
+ mType = cursor.getInt(TYPE_COLUMN);
+ mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
+ mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
+
+ // 处理通话记录相关属性
+ mPhoneNumber = "";
+ if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
+ // 获取通话记录的电话号码
+ mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
+ if (!TextUtils.isEmpty(mPhoneNumber)) {
+ // 获取联系人姓名
+ mName = Contact.getContact(context, mPhoneNumber);
+ if (mName == null) {
+ mName = mPhoneNumber; // 无联系人时显示电话号码
+ }
+ }
+ }
+
+ if (mName == null) {
+ mName = "";
+ }
+ // 检查并设置当前项在列表中的位置关系
+ checkPostion(cursor);
+ }
+
+ /**
+ * 检查当前项在列表中的位置关系,并设置相应的状态标志
+ */
+ private void checkPostion(Cursor cursor) {
+ // 检查基本位置状态
+ mIsLastItem = cursor.isLast() ? true : false;
+ mIsFirstItem = cursor.isFirst() ? true : false;
+ mIsOnlyOneItem = (cursor.getCount() == 1);
+ mIsMultiNotesFollowingFolder = false;
+ mIsOneNoteFollowingFolder = false;
+
+ // 检查是否跟随在文件夹之后
+ if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
+ int position = cursor.getPosition();
+ // 移动到前一项检查
+ if (cursor.moveToPrevious()) {
+ // 如果前一项是文件夹或系统文件夹
+ if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
+ || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
+ // 检查后续是否还有更多笔记
+ if (cursor.getCount() > (position + 1)) {
+ mIsMultiNotesFollowingFolder = true; // 文件夹后有多个笔记
+ } else {
+ mIsOneNoteFollowingFolder = true; // 文件夹后只有一个笔记
+ }
+ }
+ // 恢复到原位置
+ if (!cursor.moveToNext()) {
+ throw new IllegalStateException("cursor move to previous but can't move back");
+ }
+ }
+ }
+ }
+
+ /**
+ * 是否是文件夹下的唯一笔记
+ * @return true如果是文件夹下的唯一笔记,否则false
+ */
+ public boolean isOneFollowingFolder() {
+ return mIsOneNoteFollowingFolder;
+ }
+
+ /**
+ * 是否是文件夹下的多个笔记之一
+ * @return true如果是文件夹下的多个笔记之一,否则false
+ */
+ public boolean isMultiFollowingFolder() {
+ return mIsMultiNotesFollowingFolder;
+ }
+
+ /**
+ * 是否为列表最后一项
+ * @return true如果是最后一项,否则false
+ */
+ public boolean isLast() {
+ return mIsLastItem;
+ }
+
+ /**
+ * 获取通话记录联系人姓名
+ * @return 联系人姓名,无联系人时返回电话号码
+ */
+ public String getCallName() {
+ return mName;
+ }
+
+ /**
+ * 是否为列表第一项
+ * @return true如果是第一项,否则false
+ */
+ public boolean isFirst() {
+ return mIsFirstItem;
+ }
+
+ /**
+ * 是否为列表唯一一项
+ * @return true如果是唯一一项,否则false
+ */
+ public boolean isSingle() {
+ return mIsOnlyOneItem;
+ }
+
+ /**
+ * 获取笔记ID
+ * @return 笔记唯一标识符
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * 获取提醒日期
+ * @return 提醒日期时间戳(毫秒)
+ */
+ public long getAlertDate() {
+ return mAlertDate;
+ }
+
+ /**
+ * 获取创建日期
+ * @return 创建日期时间戳(毫秒)
+ */
+ public long getCreatedDate() {
+ return mCreatedDate;
+ }
+
+ /**
+ * 是否包含附件
+ * @return true如果包含附件,否则false
+ */
+ public boolean hasAttachment() {
+ return mHasAttachment;
+ }
+
+ /**
+ * 获取最后修改日期
+ * @return 最后修改日期时间戳(毫秒)
+ */
+ public long getModifiedDate() {
+ return mModifiedDate;
+ }
+
+ /**
+ * 获取背景颜色ID
+ * @return 背景颜色ID
+ */
+ public int getBgColorId() {
+ return mBgColorId;
+ }
+
+ /**
+ * 获取父文件夹ID
+ * @return 所属父文件夹ID
+ */
+ public long getParentId() {
+ return mParentId;
+ }
+
+ /**
+ * 获取文件夹包含的笔记数量
+ * @return 笔记数量
+ */
+ public int getNotesCount() {
+ return mNotesCount;
+ }
+
+ /**
+ * 获取文件夹ID(与getParentId()相同)
+ * @return 文件夹ID
+ */
+ public long getFolderId () {
+ return mParentId;
+ }
+
+ /**
+ * 获取笔记类型
+ * @return 笔记类型(普通笔记/文件夹/系统文件夹)
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * 获取小部件类型
+ * @return 小部件类型
+ */
+ public int getWidgetType() {
+ return mWidgetType;
+ }
+
+ /**
+ * 获取关联的小部件ID
+ * @return 小部件ID
+ */
+ public int getWidgetId() {
+ return mWidgetId;
+ }
+
+ /**
+ * 获取笔记内容摘要
+ * @return 内容摘要
+ */
+ public String getSnippet() {
+ return mSnippet;
+ }
+
+ /**
+ * 是否设置了提醒
+ * @return true如果设置了提醒,否则false
+ */
+ public boolean hasAlert() {
+ return (mAlertDate > 0);
+ }
+
+ /**
+ * 是否为通话记录
+ * @return true如果是通话记录,否则false
+ */
+ public boolean isCallRecord() {
+ return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
+ }
+
+ /**
+ * 静态方法:从Cursor中直接获取笔记类型
+ * @param cursor 包含笔记数据的Cursor
+ * @return 笔记类型
+ */
+ public static int getNoteType(Cursor cursor) {
+ return cursor.getInt(TYPE_COLUMN);
+ }
+}