Compare commits

..

1 Commits

@ -17,24 +17,40 @@
package net.micode.notes.data;
import android.net.Uri;
/**
*
* @Package: net.micode.notes.data
* @ClassName: Notes
* @Description:
*/
public class Notes {
public static final String AUTHORITY = "micode_notes";
//定义此应用ContentProvider的唯一标识
public static final String AUTHORITY = "net.micode.notes.provider";
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_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}
*/
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";
@ -46,10 +62,10 @@ public class Notes {
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
// 将不同数据类型对应到其MIMEitem类型字符串
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
public static final String ENCRYPTED_FOLDER = "vnd.android.cursor.item/encrypted_folder";
}
/**
@ -62,6 +78,7 @@ 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
@ -96,6 +113,8 @@ public class Notes {
/**
* Folder's name or text content of note
*
* 便便
* <P> Type: TEXT </P>
*/
public static final String SNIPPET = "snippet";
@ -166,19 +185,9 @@ public class Notes {
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
/**
* Flag to indicate this note is a habit note
* <P> Type : INTEGER (0/1) </P>
*/
public static final String IS_HABIT = "is_habit";
/**
* Habit configuration stored as JSON string
* <P> Type : TEXT </P>
*/
public static final String HABIT_CONFIG = "habit_config";
}
//Data表数据库列名常量接口
public interface DataColumns {
/**
* The unique ID for a row
@ -253,10 +262,12 @@ 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;
@ -269,16 +280,19 @@ 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;

@ -38,7 +38,7 @@ import net.micode.notes.data.NotesDatabaseHelper.TABLE;
*
* @Package: net.micode.notes.data
* @ClassName: NotesProvider
* @Description: java
* @Description: 便
*/
public class NotesProvider extends ContentProvider {
//uri匹配器

@ -25,6 +25,11 @@ import org.json.JSONException;
import org.json.JSONObject;
/**
* @Package: net.micode.notes.gtask.data
* @ClassName: MetaData
* @Description: Google Task便Google Task
*/
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();

@ -33,7 +33,11 @@ import java.util.ArrayList;
*
* @Package: net.micode.notes.gtask.data
* @ClassName: TaskList
* @Description: java
* @Description: Node
* 1. Task
* 2. Google Task APIJSON
* 3.
* 4.
*/
public class TaskList extends Node {
private static final String TAG = TaskList.class.getSimpleName();

@ -27,32 +27,7 @@ import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* GTaskASyncTask - Google Task
*
*
* 1. 线Google Task
* 2.
* 3.
* 4.
*
*
* - AsyncTask线
* -
* -
*
*
* 1. doInBackground()
* 2. onProgressUpdate()
* 3. onPostExecute()
* 4. cancelSync()
*
* 线
* 使AsyncTaskUI线线
*
* @author MiCode Open Source Community
* @version 1.0
*/
/**
*
* @Package: net.micode.notes.gtask.remote
@ -127,16 +102,7 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
/**
*
*
*
* 1.
* 2. GTaskManager
*
* @param unused 使Void
* @return
*/
/**
* @method doInBackground
* @description

@ -912,7 +912,7 @@ public class GTaskManager {
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
mCancelled = true;
}

@ -60,10 +60,6 @@ public class WorkingNote {
private boolean mIsDeleted;
// habit fields
private boolean mIsHabit;
private String mHabitConfig;
private NoteSettingChangedListener mNoteSettingStatusListener;// 设置变化监听器
//数据表查询字段投影 用于查询便签数据
@ -85,10 +81,7 @@ public class WorkingNote {
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
,
NoteColumns.IS_HABIT,
NoteColumns.HABIT_CONFIG
};
};
private static final int DATA_ID_COLUMN = 0;
@ -109,8 +102,6 @@ public class WorkingNote {
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
private static final int NOTE_HABIT_FLAG_COLUMN = 6;
private static final int NOTE_HABIT_CONFIG_COLUMN = 7;
// New note construct 创建新便签
private WorkingNote(Context context, long folderId) {
@ -150,17 +141,6 @@ public class WorkingNote {
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
// habit fields (may not exist on older DB versions)
try {
mIsHabit = cursor.getInt(NOTE_HABIT_FLAG_COLUMN) == 1;
} catch (Exception e) {
mIsHabit = false;
}
try {
mHabitConfig = cursor.getString(NOTE_HABIT_CONFIG_COLUMN);
} catch (Exception e) {
mHabitConfig = "";
}
}
cursor.close();
} else {
@ -305,24 +285,6 @@ public class WorkingNote {
}
}
// 设置/取消习惯便签并保存配置信息config 为 JSON 字符串,可为空)
public void setHabit(boolean isHabit, String config) {
if (mIsHabit != isHabit || (config != null && !config.equals(mHabitConfig))) {
mIsHabit = isHabit;
mHabitConfig = config == null ? "" : config;
mNote.setNoteValue(NoteColumns.IS_HABIT, String.valueOf(isHabit ? 1 : 0));
mNote.setNoteValue(NoteColumns.HABIT_CONFIG, mHabitConfig);
}
}
public boolean isHabit() {
return mIsHabit;
}
public String getHabitConfig() {
return mHabitConfig == null ? "" : mHabitConfig;
}
//设置清单模式
public void setCheckListMode(int mode) {
if (mMode != mode) {

@ -1,238 +0,0 @@
/*
* 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.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import net.micode.notes.R;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
public class DoodleDialog extends Dialog {
public interface OnDoodleSavedListener {
void onSaved(String localPath);
}
private final Context mContext;
private final OnDoodleSavedListener mListener;
private DoodleView mDoodleView;
public DoodleDialog(Context context, OnDoodleSavedListener listener) {
super(context);
mContext = context;
mListener = listener;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.doodle_title);
setContentView(createContentView());
WindowManager.LayoutParams params = getWindow().getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
getWindow().setAttributes(params);
}
private View createContentView() {
LinearLayout root = new LinearLayout(mContext);
root.setOrientation(LinearLayout.VERTICAL);
int padding = dpToPx(12);
root.setPadding(padding, padding, padding, padding);
mDoodleView = new DoodleView(mContext);
LinearLayout.LayoutParams canvasParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 0);
canvasParams.weight = 1f;
root.addView(mDoodleView, canvasParams);
LinearLayout actions = new LinearLayout(mContext);
actions.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams actionParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
actions.setLayoutParams(actionParams);
Button clearButton = new Button(mContext);
clearButton.setText(R.string.doodle_clear);
clearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDoodleView.clear();
}
});
Button cancelButton = new Button(mContext);
cancelButton.setText(android.R.string.cancel);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
Button saveButton = new Button(mContext);
saveButton.setText(R.string.doodle_save);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String path = saveDoodle();
if (!TextUtils.isEmpty(path)) {
if (mListener != null) {
mListener.onSaved(path);
}
dismiss();
} else {
Toast.makeText(mContext, R.string.doodle_save_failed, Toast.LENGTH_SHORT).show();
}
}
});
actions.addView(clearButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
actions.addView(cancelButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
actions.addView(saveButton, new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
root.addView(actions);
return root;
}
private String saveDoodle() {
Bitmap bitmap = mDoodleView.exportBitmap();
if (bitmap == null) {
return null;
}
try {
File baseDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (baseDir == null) {
baseDir = mContext.getFilesDir();
}
File appDir = new File(baseDir, "note_images");
if (!appDir.exists() && !appDir.mkdirs()) {
return null;
}
File file = new File(appDir, "doodle_" + System.currentTimeMillis() + ".png");
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
return file.getAbsolutePath();
} catch (Exception e) {
return null;
}
}
private int dpToPx(int dp) {
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
return Math.round(dm.density * dp);
}
private static class DoodleView extends View {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final List<Path> mPaths = new ArrayList<>();
private Path mCurrentPath;
DoodleView(Context context) {
super(context);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(6f);
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Path path : mPaths) {
canvas.drawPath(path, mPaint);
}
if (mCurrentPath != null) {
canvas.drawPath(mCurrentPath, mPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mCurrentPath = new Path();
mCurrentPath.moveTo(x, y);
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
if (mCurrentPath != null) {
mCurrentPath.lineTo(x, y);
invalidate();
}
return true;
case MotionEvent.ACTION_UP:
if (mCurrentPath != null) {
mCurrentPath.lineTo(x, y);
mPaths.add(mCurrentPath);
mCurrentPath = null;
invalidate();
}
return true;
default:
return false;
}
}
void clear() {
mPaths.clear();
mCurrentPath = null;
invalidate();
}
Bitmap exportBitmap() {
if (getWidth() <= 0 || getHeight() <= 0) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.WHITE);
for (Path path : mPaths) {
canvas.drawPath(path, mPaint);
}
return bitmap;
}
}
}

@ -1,218 +0,0 @@
/*
* 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;
}
}
}

@ -1,172 +0,0 @@
/*
* 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;
}
}
}

@ -1,162 +0,0 @@
package net.micode.notes.tool;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Locale;
public class TranslateUtils {
private static final String TAG = "TranslateUtils";
private static final String YOUDAO_APP_KEY = "3abfa533dbdc44d1";
private static final String YOUDAO_APP_SECRET = "aliNHKWhhTlaLjRAkOce4cHTubriEl0c";
private static final String YOUDAO_URL = "https://openapi.youdao.com/api";
public static boolean isOnline(Context ctx) {
ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = cm.getActiveNetwork();
if (network == null) return false;
NetworkCapabilities capabilities = cm.getNetworkCapabilities(network);
return capabilities != null && (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
} else {
// 兼容低版本
NetworkInfo ni = cm.getActiveNetworkInfo();
return ni != null && ni.isConnected();
}
}
public static String translateParagraph(String text, String targetLang) {
if (TextUtils.isEmpty(YOUDAO_APP_KEY) || TextUtils.isEmpty(YOUDAO_APP_SECRET)) {
Log.w(TAG, "Youdao app key/secret not configured");
return null;
}
try {
Log.d(TAG, "Starting translation: text=" + text + ", targetLang=" + targetLang);
String q = text;
String from = "auto";
String to = targetLang == null ? "en" : targetLang;
String salt = String.valueOf(System.currentTimeMillis());
String sign = md5(YOUDAO_APP_KEY + q + salt + YOUDAO_APP_SECRET);
StringBuilder sb = new StringBuilder();
sb.append("appKey=").append(urlEncode(YOUDAO_APP_KEY));
sb.append("&q=").append(urlEncode(q));
sb.append("&salt=").append(urlEncode(salt));
sb.append("&from=").append(urlEncode(from));
sb.append("&to=").append(urlEncode(to));
sb.append("&sign=").append(urlEncode(sign));
URL url = new URL(YOUDAO_URL);
Log.d(TAG, "Connecting to: " + YOUDAO_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
writer.write(sb.toString());
writer.flush();
writer.close();
int code = conn.getResponseCode();
Log.d(TAG, "Response code: " + code);
if (code != 200) {
Log.w(TAG, "Youdao response code:" + code);
// 读取错误响应
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "UTF-8"))) {
StringBuilder errorResp = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
errorResp.append(line);
}
Log.w(TAG, "Error response: " + errorResp.toString());
} catch (Exception e) {
Log.w(TAG, "Failed to read error response", e);
}
return null;
}
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder resp = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
resp.append(line);
}
br.close();
conn.disconnect();
Log.d(TAG, "Response: " + resp.toString());
JSONObject json = new JSONObject(resp.toString());
if (json.has("translation")) {
JSONArray arr = json.getJSONArray("translation");
if (arr.length() > 0) {
String result = arr.getString(0);
Log.d(TAG, "Translation result: " + result);
return result;
}
}
// fallback: try webdict or basic
if (json.has("web") && json.getJSONArray("web").length() > 0) {
JSONObject w = json.getJSONArray("web").getJSONObject(0);
if (w.has("value")) {
JSONArray v = w.getJSONArray("value");
if (v.length() > 0) {
String result = v.getString(0);
Log.d(TAG, "Web dict fallback result: " + result);
return result;
}
}
}
Log.w(TAG, "No translation found in response");
return null;
} catch (Exception e) {
Log.w(TAG, "translate error: " + e.getMessage(), e);
return null;
}
}
private static String urlEncode(String s) throws Exception {
return java.net.URLEncoder.encode(s, "UTF-8");
}
private static String md5(String s) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(s.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xff);
if (hex.length() == 1) sb.append('0');
sb.append(hex);
}
return sb.toString();
}
}

@ -1,137 +0,0 @@
package net.micode.notes.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.widget.Toast;
import android.view.View;
import net.micode.notes.R;
import java.io.InputStream;
public class BackgroundManager {
public static final int REQUEST_CODE_PICK_IMAGE = 2001; // 选图请求码
private static final String PREF_BG_TYPE = "pref_bg_type"; // 背景类型key0=颜色1=内置资源2=自定义图片URI
private static final String PREF_BG_COLOR = "pref_bg_color"; // 背景颜色key
private static final String PREF_BG_RES_ID = "pref_bg_res_id"; // 内置背景资源ID key
private static final String PREF_BG_URI = "pref_bg_uri"; // 自定义图片URI key
private static final int BG_TYPE_COLOR = 0; // 颜色类型标识
private static final int BG_TYPE_BUILTIN = 1; // 内置资源类型标识
private static final int BG_TYPE_URI = 2; // 自定义图片类型标识
private Activity mActivity;
private View mRootView;//要设置背景的根View
public BackgroundManager(Activity activity, int rootViewId) {
mActivity = activity;
mRootView = activity.findViewById(rootViewId);//绑定指定的根View
if (mRootView == null) {
mRootView = activity.getWindow().getDecorView();//若传入的View ID无效取窗口根View
}
}
// 根据偏好设置应用背景
public void applyBackgroundFromPrefs() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
int type = sp.getInt(PREF_BG_TYPE, -1);//读取背景类型
if (type == BG_TYPE_COLOR) {//颜色类型
int color = sp.getInt(PREF_BG_COLOR, 0xFFFFFFFF);//读取颜色值
applyColorBackground(color);//应用颜色背景
} else if (type == BG_TYPE_BUILTIN) {//内置资源类型
int resId = sp.getInt(PREF_BG_RES_ID, R.drawable.list_background);//读取资源ID
applyBuiltinBackground(resId);//应用内置背景
} else if (type == BG_TYPE_URI) {//自定义图片类型
String uriStr = sp.getString(PREF_BG_URI, null);//读取URI字符串
if (uriStr != null) {
applyUriBackground(Uri.parse(uriStr));//应用自定义图片背景
}
} else {
mRootView.setBackgroundResource(R.drawable.list_background);//默认背景
}
}
// 应用颜色背景并保存偏好设置
public void applyColorAndSave(int color) {
applyColorBackground(color);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);//获取默认SharedPreferences
sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_COLOR).putInt(PREF_BG_COLOR, color).commit();//保存背景类型和颜色值
}
// 应用内置背景并保存偏好设置
public void applyBuiltinAndSave(int resId) {
applyBuiltinBackground(resId);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_BUILTIN).putInt(PREF_BG_RES_ID, resId).commit();
}
// 启动图库选择图片
public void pickImageFromGallery() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
mActivity.startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
}
// 处理图库选择结果
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
Uri uri = data.getData();
if (uri != null) {
applyUriAndSave(uri);
}
return true;
}
return false;
}
// 重置为默认背景并清除偏好设置
public void resetToDefaultAndClear() {
mRootView.setBackgroundResource(R.drawable.list_background);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
sp.edit().remove(PREF_BG_TYPE).remove(PREF_BG_COLOR).remove(PREF_BG_RES_ID).remove(PREF_BG_URI).commit();
}
private void applyColorBackground(int color) {
mRootView.setBackgroundColor(color);
}
private void applyBuiltinBackground(int resId) {
mRootView.setBackgroundResource(resId);
}
// 应用URI背景
private void applyUriBackground(Uri uri) {
try {
InputStream is = mActivity.getContentResolver().openInputStream(uri);//通过内容解析器打开URI对应的输入流
if (is != null) {
Drawable d = Drawable.createFromStream(is, uri.toString());
mRootView.setBackgroundDrawable(d);
is.close();
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(mActivity, "无法加载图片", Toast.LENGTH_SHORT).show();
}
}
// 应用URI背景并保存偏好设置
private void applyUriAndSave(Uri uri) {
try {
InputStream is = mActivity.getContentResolver().openInputStream(uri);
if (is != null) {
Drawable d = Drawable.createFromStream(is, uri.toString());
mRootView.setBackgroundDrawable(d);
is.close();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
sp.edit().putInt(PREF_BG_TYPE, BG_TYPE_URI).putString(PREF_BG_URI, uri.toString()).commit();
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(mActivity, "无法加载图片", Toast.LENGTH_SHORT).show();
}
}
}

@ -1,103 +0,0 @@
/*
* 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.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.util.Log;
import net.micode.notes.data.Notes;
/**
* 广
*/
public class CallRecordReceiver extends BroadcastReceiver {
private static final String TAG = "CallRecordReceiver";
private static String mLastPhoneNumber;// 记录最后一个电话号码
private static long mCallStartTime;// 记录通话开始时间
private static boolean mIsOutgoingCall;// 是否为拨出电话
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() != null) {
Log.d(TAG, "Received broadcast: " + intent.getAction());
if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
Log.d(TAG, "Phone state changed: " + state + ", number: " + phoneNumber);
if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
// 电话响铃,记录来电号码
mLastPhoneNumber = phoneNumber;
mCallStartTime = System.currentTimeMillis();
mIsOutgoingCall = false;
Log.d(TAG, "Incoming call ringing: " + phoneNumber);
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
// 电话接通,记录开始时间
if (mCallStartTime == 0) {
mCallStartTime = System.currentTimeMillis();
}
if (phoneNumber != null) {
mLastPhoneNumber = phoneNumber;
}
Log.d(TAG, "Call answered");
} else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
// 电话挂断,创建通话记录便签
if (mLastPhoneNumber != null && mCallStartTime > 0) {
long callEndTime = System.currentTimeMillis();
long callDuration = callEndTime - mCallStartTime;
// 创建通话记录便签
Log.d(TAG, "Call ended, creating call note for: " + mLastPhoneNumber);
Intent serviceIntent = new Intent(context, CallRecordService.class);
serviceIntent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, mLastPhoneNumber);
serviceIntent.putExtra("call_date", callEndTime);
serviceIntent.putExtra("call_duration", callDuration);
// 启动服务创建通话记录便签
try {
context.startService(serviceIntent);
Log.d(TAG, "Successfully started CallRecordService");
} catch (Exception e) {
Log.e(TAG, "Error starting CallRecordService", e);
}
}
// 重置状态
mLastPhoneNumber = null;
mCallStartTime = 0;
mIsOutgoingCall = false;
Log.d(TAG, "Call state reset to idle");
}
} else if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
// 拨出电话
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
if (phoneNumber != null) {
mLastPhoneNumber = phoneNumber;
mCallStartTime = System.currentTimeMillis();
mIsOutgoingCall = true;
Log.d(TAG, "Outgoing call started: " + phoneNumber);
}
}
}
}
}

@ -1,105 +0,0 @@
/*
* 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.IntentService;
import android.content.Intent;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.ResourceParser;
/**
* 便
*/
public class CallRecordService extends IntentService {
private static final String TAG = "CallRecordService";
public CallRecordService() {
super("CallRecordService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
String phoneNumber = intent.getStringExtra(android.telephony.TelephonyManager.EXTRA_INCOMING_NUMBER);
long callDate = intent.getLongExtra("call_date", System.currentTimeMillis());
long callDuration = intent.getLongExtra("call_duration", 0);
if (phoneNumber != null) {
Log.d(TAG, "Creating call note for number: " + phoneNumber + ", date: " + callDate);
createCallNote(phoneNumber, callDate, callDuration);
}
}
}
/**
* 便
* @param phoneNumber
* @param callDate
* @param callDuration
*/
private void createCallNote(String phoneNumber, long callDate, long callDuration) {
try {
Log.d(TAG, "Starting createCallNote for number: " + phoneNumber);
// 创建空便签
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_CALL_RECORD_FOLDER,
0, Notes.TYPE_WIDGET_INVALIDE, ResourceParser.BLUE);
// 转换为通话便签
note.convertToCallNote(phoneNumber, callDate);
// 设置空格作为内容,确保能保存
note.setWorkingText(" ");
// 保存便签获取ID
long noteId = -1;
if (note.saveNote()) {
noteId = note.getNoteId();
Log.d(TAG, "Call note created successfully for: " + phoneNumber + ", noteId: " + noteId);
} else {
Log.e(TAG, "Failed to create call note for: " + phoneNumber);
return;
}
// 跳转到刚刚创建的通话记录便签的编辑视图
if (noteId > 0) {
Log.d(TAG, "Attempting to start NoteEditActivity for noteId: " + noteId);
Intent intent = new Intent(this, NoteEditActivity.class);
// 使用 ACTION_VIEW 以便 NoteEditActivity 正确处理并打开已有便签
intent.setAction(Intent.ACTION_VIEW);
// 使用正确的键名传递便签ID
intent.putExtra(Intent.EXTRA_UID, noteId);
intent.putExtra(Notes.INTENT_EXTRA_CALL_DATE, callDate);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber);
// 从服务启动 Activity需要 NEW_TASK 标志;避免清除整个任务栈
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
Log.d(TAG, "Successfully started NoteEditActivity for call note: " + noteId);
} catch (Exception e) {
Log.e(TAG, "Error starting NoteEditActivity", e);
}
}
} catch (Exception e) {
Log.e(TAG, "Error creating call note", e);
}
}
}

@ -1,218 +0,0 @@
/*
* 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;
}
}
}

@ -1,172 +0,0 @@
/*
* 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;
}
}
}

@ -1,569 +0,0 @@
/*
* 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.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
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.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.HashSet;
public class MemoryBottleDialog extends Dialog implements View.OnClickListener {
private static final String PREF_MEMORY_FOLDER_ID = "pref_memory_bottle_folder_id";
private static final List<MemoryEntry> sAllEntries = new ArrayList<>();
private static final List<MemoryEntry> sRemainingEntries = new ArrayList<>();
private static final Random sRandom = new Random();
private static long sFolderId = Long.MIN_VALUE;
private final Activity mActivity;
private Button mAddButton;
private Button mBrowseButton;
private long mMemoryFolderId = -1;
private boolean mEntriesLoaded;
private boolean mLoading;
private boolean mBrowseLoading;
private LoadTask mLoadTask;
private BrowseTask mBrowseTask;
private PendingAction mPendingAction = PendingAction.NONE;
public MemoryBottleDialog(Activity activity) {
super(activity, android.R.style.Theme_Light_NoTitleBar);
mActivity = activity;
}
@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.memory_bottle);
getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
initResources();
}
@Override
public void dismiss() {
if (mLoadTask != null) {
mLoadTask.cancel(true);
mLoadTask = null;
}
if (mBrowseTask != null) {
mBrowseTask.cancel(true);
mBrowseTask = null;
}
super.dismiss();
}
private void initResources() {
mAddButton = (Button) findViewById(R.id.btn_memory_add);
mBrowseButton = (Button) findViewById(R.id.btn_memory_browse);
mAddButton.setOnClickListener(this);
mBrowseButton.setOnClickListener(this);
updateButtonState();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_memory_add:
requestAction(PendingAction.ADD);
break;
case R.id.btn_memory_browse:
requestAction(PendingAction.BROWSE);
break;
default:
break;
}
}
private void requestAction(PendingAction action) {
if (mLoading || mBrowseLoading) {
Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
return;
}
if (mMemoryFolderId <= 0) {
mPendingAction = action;
Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
startLoadTask(action == PendingAction.BROWSE);
return;
}
if (action == PendingAction.BROWSE && !mEntriesLoaded) {
mPendingAction = action;
Toast.makeText(mActivity, R.string.memory_bottle_loading, Toast.LENGTH_SHORT).show();
startLoadTask(true);
return;
}
if (action == PendingAction.ADD) {
showAddDialog();
} else if (action == PendingAction.BROWSE) {
browseMemory();
}
}
private void startLoadTask(boolean loadEntries) {
if (mLoadTask != null) {
return;
}
setLoading(true);
mLoadTask = new LoadTask(this, loadEntries);
mLoadTask.execute();
}
private void setLoading(boolean loading) {
mLoading = loading;
updateButtonState();
}
private void setBrowseLoading(boolean loading) {
mBrowseLoading = loading;
updateButtonState();
}
private void updateButtonState() {
boolean enabled = !(mLoading || mBrowseLoading);
if (mAddButton != null) {
mAddButton.setEnabled(enabled);
}
if (mBrowseButton != null) {
mBrowseButton.setEnabled(enabled);
}
}
private void showAddDialog() {
final EditText editText = new EditText(mActivity);
int padding = (int) (mActivity.getResources().getDisplayMetrics().density * 16);
editText.setPadding(padding, padding, padding, padding);
editText.setGravity(Gravity.TOP | Gravity.START);
editText.setMinLines(4);
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
editText.setHint(R.string.memory_bottle_add_hint);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(R.string.memory_bottle_add_title);
builder.setView(editText);
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
final AlertDialog dialog = builder.show();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = editText.getText().toString().trim();
if (TextUtils.isEmpty(content)) {
Toast.makeText(mActivity, R.string.memory_bottle_empty_input,
Toast.LENGTH_SHORT).show();
return;
}
if (createMemoryNote(content)) {
dialog.dismiss();
}
}
});
}
private boolean createMemoryNote(String content) {
if (mMemoryFolderId <= 0) {
Toast.makeText(mActivity, R.string.memory_bottle_folder_error, Toast.LENGTH_SHORT).show();
return false;
}
WorkingNote note = WorkingNote.createEmptyNote(mActivity, mMemoryFolderId,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.getDefaultBgId(mActivity));
note.setWorkingText(content);
if (!note.saveNote()) {
Toast.makeText(mActivity, R.string.memory_bottle_save_failed, Toast.LENGTH_SHORT).show();
return false;
}
long noteId = note.getNoteId();
long createdDate = queryNoteCreatedDate(noteId);
MemoryEntry entry = new MemoryEntry(noteId, createdDate, content);
sAllEntries.add(entry);
sRemainingEntries.add(entry);
mEntriesLoaded = true;
Toast.makeText(mActivity, R.string.memory_bottle_save_success, Toast.LENGTH_SHORT).show();
return true;
}
private long queryNoteCreatedDate(long noteId) {
Cursor cursor = mActivity.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
new String[] { NoteColumns.CREATED_DATE },
null, null, null);
if (cursor == null) {
return System.currentTimeMillis();
}
try {
if (cursor.moveToFirst()) {
return cursor.getLong(0);
}
} finally {
cursor.close();
}
return System.currentTimeMillis();
}
private void browseMemory() {
if (sAllEntries.isEmpty()) {
Toast.makeText(mActivity, R.string.memory_bottle_empty, Toast.LENGTH_SHORT).show();
return;
}
if (sRemainingEntries.isEmpty()) {
showBrowseFinishedDialog();
return;
}
showRandomEntry();
}
private void showBrowseFinishedDialog() {
new AlertDialog.Builder(mActivity)
.setMessage(R.string.memory_bottle_browse_done)
.setPositiveButton(R.string.memory_bottle_restart, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
resetRemainingEntries();
showRandomEntry();
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void resetRemainingEntries() {
sRemainingEntries.clear();
sRemainingEntries.addAll(sAllEntries);
}
private void showRandomEntry() {
int index = sRandom.nextInt(sRemainingEntries.size());
MemoryEntry entry = sRemainingEntries.remove(index);
startBrowseTask(entry);
}
private void startBrowseTask(MemoryEntry entry) {
if (mBrowseTask != null) {
return;
}
setBrowseLoading(true);
mBrowseTask = new BrowseTask(this, entry);
mBrowseTask.execute();
}
private String formatEntryMessage(long createdDate, String content) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
String date = format.format(new Date(createdDate));
return mActivity.getString(R.string.memory_bottle_entry_format, date, content);
}
private long ensureMemoryFolder() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
long storedId = sp.getLong(PREF_MEMORY_FOLDER_ID, Long.MIN_VALUE);
if (storedId > 0 && DataUtils.visibleInNoteDatabase(mActivity.getContentResolver(),
storedId, Notes.TYPE_FOLDER)) {
return storedId;
}
String folderName = mActivity.getString(R.string.memory_bottle_folder_name);
long folderId = queryFolderIdByName(folderName);
if (folderId > 0) {
sp.edit().putLong(PREF_MEMORY_FOLDER_ID, folderId).commit();
return folderId;
}
folderId = createMemoryFolder(folderName);
if (folderId > 0) {
sp.edit().putLong(PREF_MEMORY_FOLDER_ID, folderId).commit();
return folderId;
}
return -1;
}
private long queryFolderIdByName(String name) {
ContentResolver resolver = mActivity.getContentResolver();
Cursor cursor = resolver.query(
Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.ID },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?"
+ " AND " + NoteColumns.SNIPPET + "=?",
new String[] { String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER), name },
null);
if (cursor == null) {
return 0;
}
try {
if (cursor.moveToFirst()) {
return cursor.getLong(0);
}
} finally {
cursor.close();
}
return 0;
}
private long createMemoryFolder(String name) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.PARENT_ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
android.net.Uri uri = mActivity.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
if (uri == null) {
return 0;
}
try {
return Long.parseLong(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
return 0;
}
}
private String queryNoteContent(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(
Notes.CONTENT_DATA_URI,
new String[] { DataColumns.CONTENT },
DataColumns.NOTE_ID + "=? AND " + DataColumns.MIME_TYPE + "=?",
new String[] { String.valueOf(noteId), TextNote.CONTENT_ITEM_TYPE },
null);
if (cursor == null) {
return "";
}
try {
if (cursor.moveToFirst()) {
return cursor.getString(0);
}
} finally {
cursor.close();
}
return "";
}
private List<MemoryEntry> loadEntriesFromDatabase(long folderId) {
List<MemoryEntry> entries = new ArrayList<>();
ContentResolver resolver = mActivity.getContentResolver();
Cursor cursor = resolver.query(
Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.ID, NoteColumns.CREATED_DATE },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(Notes.TYPE_NOTE), String.valueOf(folderId) },
NoteColumns.CREATED_DATE + " DESC");
if (cursor == null) {
return entries;
}
try {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(0);
long createdDate = cursor.getLong(1);
entries.add(new MemoryEntry(noteId, createdDate, ""));
}
} finally {
cursor.close();
}
return entries;
}
private static final class MemoryEntry {
private final long id;
private final long createdDate;
private final String content;
private MemoryEntry(long id, long createdDate, String content) {
this.id = id;
this.createdDate = createdDate;
this.content = content;
}
}
private static final class LoadResult {
private final long folderId;
private final List<MemoryEntry> entries;
private final boolean loadedEntries;
private LoadResult(long folderId, List<MemoryEntry> entries, boolean loadedEntries) {
this.folderId = folderId;
this.entries = entries;
this.loadedEntries = loadedEntries;
}
}
private static final class LoadTask extends AsyncTask<Void, Void, LoadResult> {
private final WeakReference<MemoryBottleDialog> mRef;
private final boolean mLoadEntries;
private LoadTask(MemoryBottleDialog dialog, boolean loadEntries) {
mRef = new WeakReference<>(dialog);
mLoadEntries = loadEntries;
}
@Override
protected LoadResult doInBackground(Void... params) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null) {
return null;
}
long folderId = dialog.ensureMemoryFolder();
List<MemoryEntry> entries = new ArrayList<>();
if (folderId > 0 && mLoadEntries) {
entries = dialog.loadEntriesFromDatabase(folderId);
}
return new LoadResult(folderId, entries, mLoadEntries);
}
@Override
protected void onPostExecute(LoadResult result) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null || !dialog.isShowing()) {
return;
}
dialog.mLoadTask = null;
dialog.setLoading(false);
if (result == null || result.folderId <= 0) {
Toast.makeText(dialog.mActivity, R.string.memory_bottle_folder_error,
Toast.LENGTH_SHORT).show();
dialog.mPendingAction = PendingAction.NONE;
return;
}
dialog.mMemoryFolderId = result.folderId;
sFolderId = result.folderId;
if (result.loadedEntries) {
sAllEntries.clear();
sAllEntries.addAll(result.entries);
sRemainingEntries.clear();
sRemainingEntries.addAll(result.entries);
dialog.mEntriesLoaded = true;
} else if (sFolderId == result.folderId) {
dialog.mEntriesLoaded = !sAllEntries.isEmpty();
}
PendingAction pending = dialog.mPendingAction;
dialog.mPendingAction = PendingAction.NONE;
if (pending == PendingAction.ADD) {
dialog.showAddDialog();
} else if (pending == PendingAction.BROWSE) {
dialog.browseMemory();
}
}
}
private static final class BrowseResult {
private final MemoryEntry entry;
private final String content;
private BrowseResult(MemoryEntry entry, String content) {
this.entry = entry;
this.content = content;
}
}
private static final class BrowseTask extends AsyncTask<Void, Void, BrowseResult> {
private final WeakReference<MemoryBottleDialog> mRef;
private final MemoryEntry mEntry;
private BrowseTask(MemoryBottleDialog dialog, MemoryEntry entry) {
mRef = new WeakReference<>(dialog);
mEntry = entry;
}
@Override
protected BrowseResult doInBackground(Void... params) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null) {
return null;
}
String content = mEntry.content;
if (TextUtils.isEmpty(content)) {
content = dialog.queryNoteContent(dialog.mActivity.getContentResolver(), mEntry.id);
}
if (TextUtils.isEmpty(content)) {
content = dialog.mActivity.getString(R.string.memory_bottle_missing_content);
}
return new BrowseResult(mEntry, content);
}
@Override
protected void onPostExecute(BrowseResult result) {
MemoryBottleDialog dialog = mRef.get();
if (dialog == null || !dialog.isShowing()) {
return;
}
dialog.mBrowseTask = null;
dialog.setBrowseLoading(false);
if (result == null) {
return;
}
String message = dialog.formatEntryMessage(result.entry.createdDate, result.content);
new AlertDialog.Builder(dialog.mActivity)
.setTitle(R.string.memory_bottle_title)
.setMessage(message)
.setPositiveButton(R.string.memory_bottle_close, null)
.setNegativeButton(R.string.memory_bottle_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
dialog.deleteMemoryEntry(result.entry);
}
})
.show();
}
}
private void deleteMemoryEntry(MemoryEntry entry) {
if (mMemoryFolderId <= 0) {
Toast.makeText(mActivity, R.string.memory_bottle_folder_error, Toast.LENGTH_SHORT).show();
return;
}
HashSet<Long> ids = new HashSet<Long>();
ids.add(entry.id);
if (DataUtils.batchMoveToTrash(mActivity.getContentResolver(), ids, mMemoryFolderId)) {
sAllEntries.remove(entry);
sRemainingEntries.remove(entry);
Toast.makeText(mActivity, R.string.memory_bottle_delete_success, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mActivity, R.string.memory_bottle_delete_failed, Toast.LENGTH_SHORT).show();
}
}
private enum PendingAction {
NONE,
ADD,
BROWSE
}
}

File diff suppressed because it is too large Load Diff

@ -1,192 +0,0 @@
/*
* 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.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.HashSet;
public class TrashManager {
private static final String TAG = "TrashManager";
private final Context mContext;
private final ContentResolver mResolver;
private final Callback mCallback;
public interface Callback {
void onWidgetsNeedUpdate(HashSet<AppWidgetAttribute> widgets);
void onListChanged();
void onActionModeFinished();
void onRestoreInvalid();
}
public TrashManager(Context context, ContentResolver resolver, Callback callback) {
mContext = context;
mResolver = resolver;
mCallback = callback;
}
public void cleanupExpiredTrash() {
long expireTime = System.currentTimeMillis() - 24L * 60L * 60L * 1000L;
mResolver.delete(Notes.CONTENT_NOTE_URI,
NoteColumns.PARENT_ID + "=? AND " + NoteColumns.MODIFIED_DATE + "<?",
new String[] { String.valueOf(Notes.ID_TRASH_FOLER), String.valueOf(expireTime) });
}
public void batchDelete(final boolean inTrash, final HashSet<Long> ids,
final HashSet<AppWidgetAttribute> widgets, final long originFolderId) {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
if (inTrash) {
if (!DataUtils.batchDeleteNotes(mResolver, ids)) {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
if (!DataUtils.batchMoveToTrash(mResolver, ids, originFolderId)) {
Log.e(TAG, "Move notes to trash folder error");
}
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> resultWidgets) {
if (mCallback != null) {
mCallback.onWidgetsNeedUpdate(resultWidgets);
mCallback.onListChanged();
mCallback.onActionModeFinished();
}
}
}.execute();
}
public void restoreSelected(final HashSet<Long> ids, final HashSet<AppWidgetAttribute> widgets) {
new AsyncTask<Void, Void, Boolean>() {
protected Boolean doInBackground(Void... params) {
boolean hasInvalid = false;
long now = System.currentTimeMillis();
for (long id : ids) {
Cursor cursor = mResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
new String[] { NoteColumns.ORIGIN_PARENT_ID, NoteColumns.TYPE },
null, null, null);
if (cursor == null) {
continue;
}
long originParent = Notes.ID_ROOT_FOLDER;
int type = Notes.TYPE_NOTE;
try {
if (cursor.moveToFirst()) {
originParent = cursor.getLong(0);
type = cursor.getInt(1);
}
} finally {
cursor.close();
}
long targetParent = resolveRestoreParent(originParent);
if (targetParent != originParent) {
hasInvalid = true;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, targetParent);
values.put(NoteColumns.ORIGIN_PARENT_ID, 0);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, now);
mResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
values, null, null);
if (type == Notes.TYPE_FOLDER) {
restoreNotesForFolder(id, now);
}
}
return hasInvalid;
}
@Override
protected void onPostExecute(Boolean hasInvalid) {
if (mCallback != null) {
if (hasInvalid != null && hasInvalid) {
mCallback.onRestoreInvalid();
}
mCallback.onWidgetsNeedUpdate(widgets);
mCallback.onListChanged();
mCallback.onActionModeFinished();
}
}
}.execute();
}
public HashSet<AppWidgetAttribute> moveFolderToTrash(long folderId, long originFolderId) {
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mResolver, folderId);
DataUtils.moveNotesToTrashForFolder(mResolver, folderId);
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
if (!DataUtils.batchMoveToTrash(mResolver, ids, originFolderId)) {
Log.e(TAG, "Move folder to trash error");
}
return widgets;
}
private void restoreNotesForFolder(long folderId, long now) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, folderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, 0);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, now);
mResolver.update(Notes.CONTENT_NOTE_URI, values,
NoteColumns.PARENT_ID + "=? AND " + NoteColumns.ORIGIN_PARENT_ID + "=?",
new String[] { String.valueOf(Notes.ID_TRASH_FOLER), String.valueOf(folderId) });
}
private long resolveRestoreParent(long originParentId) {
if (originParentId == Notes.ID_ROOT_FOLDER || originParentId == Notes.ID_CALL_RECORD_FOLDER) {
return originParentId;
}
if (originParentId <= 0) {
return Notes.ID_ROOT_FOLDER;
}
Cursor cursor = mResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, originParentId),
new String[] { NoteColumns.ID, NoteColumns.PARENT_ID },
NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.ID_TRASH_FOLER) },
null);
if (cursor == null) {
return Notes.ID_ROOT_FOLDER;
}
try {
if (cursor.moveToFirst()) {
return originParentId;
}
} finally {
cursor.close();
}
return Notes.ID_ROOT_FOLDER;
}
}

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#88555555" />
<item android:state_selected="true" android:color="#ff999999" />
<item android:color="#ff000000" />
</selector>

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save