Merge branch 'master' of https://bdgit.educoder.net/p6vxzahlf/Notes into wangyijia_branch

# Conflicts:
#	src/notes/tool/DataUtils.java
#	src/notes/tool/TranslateUtils.java
#	src/notes/ui/BackgroundManager.java
#	src/notes/ui/CallRecordReceiver.java
#	src/notes/ui/CallRecordService.java
#	src/notes/ui/EncryptedFolderManager.java
#	src/notes/ui/ImageInsertHelper.java
#	src/notes/ui/MemoryBottleDialog.java
#	src/notes/ui/NoteEditActivity.java
#	src/notes/ui/NoteEditText.java
#	src/notes/ui/NotesListActivity.java
#	src/notes/ui/NotesListAdapter.java
#	src/notes/ui/NotesListItem.java
#	src/notes/ui/NotesPreferenceActivity.java
#	src/notes/ui/TrashManager.java
#	src/ui/NoteItemData.java
pull/30/head
white-yj8109 4 weeks ago
commit 053d98bf6f

@ -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
*
* 便便
* <P> Type: TEXT </P>
*/
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
* <P> Type: Integer 1:check list mode 0: normal mode </P>
* 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
* <P> Type: INTEGER (long) </P>
* data1
*/
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
* data3
*/
public static final String PHONE_NUMBER = DATA3;

@ -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" +
")";
/**datasql
@ -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 ''");
}
}

@ -323,15 +323,25 @@ public class DataUtils {
}
return ids;
}
<<<<<<< HEAD:src/notes/tool/DataUtils.java
public static boolean batchMoveToTrash(ContentResolver resolver, HashSet<Long> ids, long originFolderId) {
if (ids == null || ids.isEmpty()) {
Log.d(TAG, "the ids is null or empty");
=======
public static boolean batchMoveToTrash(ContentResolver resolver, HashSet<Long> ids,
long originFolderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
>>>>>>> c2e9e136026fe8d21df7cf360e2d6ea397c67d5f:src/tool/DataUtils.java
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
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
}

@ -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:

@ -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;
}
}
}

@ -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 "<img src=\"" + imgUrl + "\" width=\"200\" height=\"200\"/><br/>";
}
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;
}
}
}

@ -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

@ -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<Integer, Boolean>();

@ -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);

@ -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);

@ -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;
/**
* CursorJava
*
*/
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 truefalse
*/
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
/**
*
* @return truefalse
*/
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
/**
*
* @return truefalse
*/
public boolean isLast() {
return mIsLastItem;
}
/**
*
* @return
*/
public String getCallName() {
return mName;
}
/**
*
* @return truefalse
*/
public boolean isFirst() {
return mIsFirstItem;
}
/**
*
* @return truefalse
*/
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 truefalse
*/
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;
}
/**
* IDgetParentId()
* @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 truefalse
*/
public boolean hasAlert() {
return (mAlertDate > 0);
}
/**
*
* @return truefalse
*/
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);
}
}
Loading…
Cancel
Save