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); + } +}