Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
22eb6a14ae | 3 months ago |
@ -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,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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
|
Before Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 245 B |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 443 B |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 554 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |