新增回收站功能 #10

Merged
psq5hzxpo merged 1 commits from luhaozhe_branch into master 1 month ago

@ -112,6 +112,14 @@
</intent-filter>
</receiver>
<receiver
android:name=".ui.TrashCleanupReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
@ -147,10 +155,10 @@
</activity>
<activity
android:name=".ui.NumericPasswordActivity"
android:label="@string/numeric_password_title"
android:theme="@style/NoteTheme"
android:exported="true">
android:name=".ui.TrashActivity"
android:label="@string/trash_folder_name"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
</activity>
<service

@ -0,0 +1,89 @@
/*
* 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.data;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes.NoteColumns;
/**
*
* <p>
*
* </p>
*/
public class TrashException extends Exception {
private static final String TAG = "TrashException";
/**
*
*/
public enum ErrorType {
INVALID_NOTE_ID("Invalid note ID"),
SYSTEM_FOLDER_ERROR("Cannot operate on system folder"),
NOTE_NOT_FOUND("Note not found"),
DATABASE_ERROR("Database operation failed"),
ALREADY_IN_TRASH("Note is already in trash"),
NOT_IN_TRASH("Note is not in trash"),
EMPTY_TRASH("Trash is already empty"),
UNKNOWN_ERROR("Unknown error occurred");
private final String message;
ErrorType(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
private ErrorType errorType;
private long noteId;
public TrashException(ErrorType errorType) {
super(errorType.getMessage());
this.errorType = errorType;
this.noteId = -1;
Log.e(TAG, "TrashException: " + errorType.getMessage());
}
public TrashException(ErrorType errorType, long noteId) {
super(errorType.getMessage() + " (ID: " + noteId + ")");
this.errorType = errorType;
this.noteId = noteId;
Log.e(TAG, "TrashException: " + errorType.getMessage() + " (ID: " + noteId + ")");
}
public TrashException(ErrorType errorType, Throwable cause) {
super(errorType.getMessage(), cause);
this.errorType = errorType;
this.noteId = -1;
Log.e(TAG, "TrashException: " + errorType.getMessage(), cause);
}
public ErrorType getErrorType() {
return errorType;
}
public long getNoteId() {
return noteId;
}
}

@ -0,0 +1,433 @@
/*
* 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.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import net.micode.notes.data.Notes.NoteColumns;
/**
*
* <p>
*
* - 便
* - 便
* -
* - 便
* -
* - 便
* </p>
*/
public class TrashManager {
private static final String TAG = "TrashManager";
private static final long TRASH_AUTO_CLEANUP_DAYS = 30; // 回收站自动清理天数30天
private Context mContext;
private NotesDatabaseHelper mDatabaseHelper;
private static TrashManager sInstance;
private TrashManager(Context context) {
mContext = context.getApplicationContext();
mDatabaseHelper = NotesDatabaseHelper.getInstance(context);
}
public static synchronized TrashManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new TrashManager(context);
}
return sInstance;
}
/**
* 便
* @param noteId 便ID
* @return
* @throws TrashException
*/
public boolean moveToTrash(long noteId) throws TrashException {
if (noteId <= 0) {
throw new TrashException(TrashException.ErrorType.INVALID_NOTE_ID, noteId);
}
if (noteId == Notes.ID_ROOT_FOLDER || noteId == Notes.ID_CALL_RECORD_FOLDER) {
throw new TrashException(TrashException.ErrorType.SYSTEM_FOLDER_ERROR, noteId);
}
if (isInTrash(noteId)) {
throw new TrashException(TrashException.ErrorType.ALREADY_IN_TRASH, noteId);
}
try {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
int result = mContext.getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)});
if (result == 0) {
throw new TrashException(TrashException.ErrorType.NOTE_NOT_FOUND, noteId);
}
return true;
} catch (Exception e) {
if (e instanceof TrashException) {
throw (TrashException) e;
}
throw new TrashException(TrashException.ErrorType.DATABASE_ERROR, e);
}
}
/**
* 便
* @param noteIds 便ID
* @return
* @throws TrashException
*/
public boolean batchMoveToTrash(long[] noteIds) throws TrashException {
if (noteIds == null || noteIds.length == 0) {
return true;
}
boolean allSuccess = true;
for (long noteId : noteIds) {
try {
moveToTrash(noteId);
} catch (TrashException e) {
Log.e(TAG, "Failed to move note " + noteId + " to trash: " + e.getMessage());
allSuccess = false;
}
}
return allSuccess;
}
/**
* 便
* @param noteId 便ID
* @return
* @throws TrashException
*/
public boolean restoreFromTrash(long noteId) throws TrashException {
if (noteId <= 0) {
throw new TrashException(TrashException.ErrorType.INVALID_NOTE_ID, noteId);
}
if (!isInTrash(noteId)) {
throw new TrashException(TrashException.ErrorType.NOT_IN_TRASH, noteId);
}
try {
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.ORIGIN_PARENT_ID},
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)},
null);
long targetParentId = Notes.ID_ROOT_FOLDER;
if (cursor != null && cursor.moveToFirst()) {
targetParentId = cursor.getLong(0);
if (targetParentId == 0 || targetParentId == Notes.ID_TRASH_FOLER) {
targetParentId = Notes.ID_ROOT_FOLDER;
}
cursor.close();
}
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, targetParentId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
int result = mContext.getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)});
if (result == 0) {
throw new TrashException(TrashException.ErrorType.NOTE_NOT_FOUND, noteId);
}
return true;
} catch (Exception e) {
if (e instanceof TrashException) {
throw (TrashException) e;
}
throw new TrashException(TrashException.ErrorType.DATABASE_ERROR, e);
}
}
/**
* 便
* @param noteIds 便ID
* @return
* @throws TrashException
*/
public boolean batchRestoreFromTrash(long[] noteIds) throws TrashException {
if (noteIds == null || noteIds.length == 0) {
return true;
}
boolean allSuccess = true;
for (long noteId : noteIds) {
try {
restoreFromTrash(noteId);
} catch (TrashException e) {
Log.e(TAG, "Failed to restore note " + noteId + ": " + e.getMessage());
allSuccess = false;
}
}
return allSuccess;
}
/**
* 便
* @param noteId 便ID
* @return
* @throws TrashException
*/
public boolean permanentlyDelete(long noteId) throws TrashException {
if (noteId <= 0) {
throw new TrashException(TrashException.ErrorType.INVALID_NOTE_ID, noteId);
}
try {
int result = mContext.getContentResolver().delete(
Notes.CONTENT_NOTE_URI,
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)});
if (result == 0) {
throw new TrashException(TrashException.ErrorType.NOTE_NOT_FOUND, noteId);
}
return true;
} catch (Exception e) {
if (e instanceof TrashException) {
throw (TrashException) e;
}
throw new TrashException(TrashException.ErrorType.DATABASE_ERROR, e);
}
}
/**
* 便
* @param noteIds 便ID
* @return
* @throws TrashException
*/
public boolean batchPermanentlyDelete(long[] noteIds) throws TrashException {
if (noteIds == null || noteIds.length == 0) {
return true;
}
boolean allSuccess = true;
for (long noteId : noteIds) {
try {
permanentlyDelete(noteId);
} catch (TrashException e) {
Log.e(TAG, "Failed to permanently delete note " + noteId + ": " + e.getMessage());
allSuccess = false;
}
}
return allSuccess;
}
/**
*
* @return
* @throws TrashException
*/
public boolean emptyTrash() throws TrashException {
try {
int result = mContext.getContentResolver().delete(
Notes.CONTENT_NOTE_URI,
NoteColumns.PARENT_ID + "=?",
new String[]{String.valueOf(Notes.ID_TRASH_FOLER)});
if (result == 0) {
throw new TrashException(TrashException.ErrorType.EMPTY_TRASH);
}
Log.d(TAG, "Empty trash: deleted " + result + " notes");
return true;
} catch (Exception e) {
if (e instanceof TrashException) {
throw (TrashException) e;
}
throw new TrashException(TrashException.ErrorType.DATABASE_ERROR, e);
}
}
/**
* 便
* @return 便
*/
public int getTrashCount() {
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{"COUNT(*)"},
NoteColumns.PARENT_ID + "=?",
new String[]{String.valueOf(Notes.ID_TRASH_FOLER)},
null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getInt(0);
}
} catch (Exception e) {
Log.e(TAG, "Get trash count failed: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
}
return 0;
}
/**
* 便
* @param noteId 便ID
* @return
*/
public boolean isInTrash(long noteId) {
if (noteId <= 0) {
return false;
}
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.PARENT_ID},
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)},
null);
if (cursor != null && cursor.moveToFirst()) {
long parentId = cursor.getLong(0);
return parentId == Notes.ID_TRASH_FOLER;
}
} catch (Exception e) {
Log.e(TAG, "Check if note in trash failed: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
}
return false;
}
/**
* 便ID
* @param noteId 便ID
* @return ID-1
*/
public long getOriginParentId(long noteId) {
if (noteId <= 0) {
return -1;
}
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.ORIGIN_PARENT_ID},
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)},
null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
}
} catch (Exception e) {
Log.e(TAG, "Get origin parent id failed: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
}
return -1;
}
/**
* 便
* 30便
* @return 便
*/
public int autoCleanupExpiredNotes() {
long expireTime = System.currentTimeMillis() - (TRASH_AUTO_CLEANUP_DAYS * 24 * 60 * 60 * 1000L);
try {
int result = mContext.getContentResolver().delete(
Notes.CONTENT_NOTE_URI,
NoteColumns.PARENT_ID + "=? AND " + NoteColumns.MODIFIED_DATE + "<?",
new String[]{
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(expireTime)
});
if (result > 0) {
Log.d(TAG, "Auto cleanup: deleted " + result + " expired notes from trash");
}
return result;
} catch (Exception e) {
Log.e(TAG, "Auto cleanup failed: " + e.getMessage());
return 0;
}
}
/**
* 便
* @return 便
*/
public int getExpiredNotesCount() {
long expireTime = System.currentTimeMillis() - (TRASH_AUTO_CLEANUP_DAYS * 24 * 60 * 60 * 1000L);
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{"COUNT(*)"},
NoteColumns.PARENT_ID + "=? AND " + NoteColumns.MODIFIED_DATE + "<?",
new String[]{
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(expireTime)
},
null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getInt(0);
}
} catch (Exception e) {
Log.e(TAG, "Get expired notes count failed: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
}
return 0;
}
}

@ -529,20 +529,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
if (!isSyncMode()) {
// if not synced, delete notes directly
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// in sync mode, we'll move the deleted note into the trash
// folder
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
// Always move notes to trash folder for better user experience
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
return widgets;
}
@ -567,18 +557,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
if (!isSyncMode()) {
// if not synced, delete folder directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// in sync mode, we'll move the deleted folder into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
// Always move folder to trash folder for better user experience
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
@ -858,9 +845,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
createNewNote();
} else if (item.getItemId() == R.id.menu_search) {
onSearchRequested();
} else if (item.getItemId() == R.id.menu_trash) {
startTrashActivity();
}
return true;
}
private void startTrashActivity() {
Intent intent = new Intent(this, TrashActivity.class);
startActivity(intent);
}
@Override
public boolean onSearchRequested() {

@ -0,0 +1,355 @@
/*
* 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.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
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.NoteColumns;
import net.micode.notes.data.TrashException;
import net.micode.notes.data.TrashManager;
/**
* Activity
* <p>
* 便
* </p>
*/
public class TrashActivity extends Activity {
private static final String TAG = "TrashActivity";
private static final int TRASH_LIST_QUERY_TOKEN = 0;
private ListView mTrashListView;
private NotesListAdapter mTrashListAdapter;
private BackgroundQueryHandler mBackgroundQueryHandler;
private ContentResolver mContentResolver;
private TrashManager mTrashManager;
private ModeCallback mModeCallback;
private TextView mEmptyView;
private TextView mTitleBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.trash_list);
initResources();
startAsyncTrashQuery();
}
private void initResources() {
mContentResolver = getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(mContentResolver);
mTrashManager = TrashManager.getInstance(this);
mModeCallback = new ModeCallback();
mTrashListView = (ListView) findViewById(R.id.trash_list);
mEmptyView = (TextView) findViewById(R.id.empty_view);
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mTrashListView.setEmptyView(mEmptyView);
mTrashListView.setOnItemClickListener(new OnTrashItemClickListener());
mTrashListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mTrashListView.setMultiChoiceModeListener(mModeCallback);
mTrashListAdapter = new NotesListAdapter(this);
mTrashListView.setAdapter(mTrashListAdapter);
mTitleBar.setText(R.string.trash_folder_name);
}
private void startAsyncTrashQuery() {
String selection = NoteColumns.PARENT_ID + "=?";
mBackgroundQueryHandler.startQuery(
TRASH_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
NoteItemData.PROJECTION,
selection,
new String[]{String.valueOf(Notes.ID_TRASH_FOLER)},
NoteColumns.MODIFIED_DATE + " DESC");
}
private class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (token == TRASH_LIST_QUERY_TOKEN) {
mTrashListAdapter.changeCursor(cursor);
updateEmptyView();
}
}
}
private void updateEmptyView() {
int count = mTrashManager.getTrashCount();
if (count == 0) {
mEmptyView.setText(R.string.trash_empty);
mEmptyView.setVisibility(View.VISIBLE);
} else {
mEmptyView.setVisibility(View.GONE);
}
}
private class OnTrashItemClickListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
if (mTrashListAdapter.isInChoiceMode()) {
if (item.getType() == Notes.TYPE_NOTE) {
position = position - mTrashListView.getHeaderViewsCount();
mModeCallback.onItemCheckedStateChanged(null, position, id,
!mTrashListAdapter.isSelectedItem(position));
}
} else {
showNoteDetailDialog(item);
}
}
}
}
private void showNoteDetailDialog(final NoteItemData item) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(item.getSnippet());
builder.setMessage(item.getSnippet());
builder.setPositiveButton(R.string.menu_restore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
restoreNote(item.getId());
}
});
builder.setNegativeButton(R.string.menu_permanently_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
permanentlyDeleteNote(item.getId());
}
});
builder.setNeutralButton(android.R.string.cancel, null);
builder.show();
}
private void restoreNote(long noteId) {
try {
mTrashManager.restoreFromTrash(noteId);
Toast.makeText(this, R.string.toast_restore_success, Toast.LENGTH_SHORT).show();
startAsyncTrashQuery();
} catch (TrashException e) {
Toast.makeText(this, getErrorMessage(e), Toast.LENGTH_SHORT).show();
}
}
private void permanentlyDeleteNote(long noteId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.alert_title_permanently_delete);
builder.setMessage(R.string.alert_message_permanently_delete);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
mTrashManager.permanentlyDelete(noteId);
Toast.makeText(TrashActivity.this, R.string.toast_delete_success, Toast.LENGTH_SHORT).show();
startAsyncTrashQuery();
} catch (TrashException e) {
Toast.makeText(TrashActivity.this, getErrorMessage(e), Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
private class ModeCallback implements ListView.MultiChoiceModeListener {
private ActionMode mActionMode;
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.trash_options, menu);
mActionMode = mode;
mTrashListAdapter.setChoiceMode(true);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
int selectedCount = mTrashListAdapter.getSelectedCount();
if (selectedCount == 0) {
Toast.makeText(TrashActivity.this, R.string.menu_select_none, Toast.LENGTH_SHORT).show();
return true;
}
int itemId = item.getItemId();
if (itemId == R.id.menu_restore) {
restoreSelectedNotes();
} else if (itemId == R.id.menu_permanently_delete) {
permanentlyDeleteSelectedNotes();
} else if (itemId == R.id.menu_empty_trash) {
emptyTrash();
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mTrashListAdapter.setChoiceMode(false);
mActionMode = null;
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
mTrashListAdapter.setCheckedItem(position, checked);
if (mActionMode != null) {
int count = mTrashListAdapter.getSelectedCount();
mActionMode.setTitle(getResources().getString(R.string.menu_select_title, count));
}
}
public void finishActionMode() {
if (mActionMode != null) {
mActionMode.finish();
}
}
}
private void restoreSelectedNotes() {
long[] selectedIds = getSelectedNoteIds();
if (selectedIds.length > 0) {
try {
mTrashManager.batchRestoreFromTrash(selectedIds);
Toast.makeText(this, getString(R.string.toast_restore_multiple, selectedIds.length),
Toast.LENGTH_SHORT).show();
mModeCallback.finishActionMode();
startAsyncTrashQuery();
} catch (TrashException e) {
Toast.makeText(this, getErrorMessage(e), Toast.LENGTH_SHORT).show();
}
}
}
private void permanentlyDeleteSelectedNotes() {
final long[] selectedIds = getSelectedNoteIds();
if (selectedIds.length > 0) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.alert_title_permanently_delete);
builder.setMessage(getString(R.string.alert_message_permanently_delete_multiple, selectedIds.length));
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
mTrashManager.batchPermanentlyDelete(selectedIds);
Toast.makeText(TrashActivity.this,
getString(R.string.toast_delete_multiple, selectedIds.length),
Toast.LENGTH_SHORT).show();
mModeCallback.finishActionMode();
startAsyncTrashQuery();
} catch (TrashException e) {
Toast.makeText(TrashActivity.this, getErrorMessage(e), Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
}
private void emptyTrash() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.alert_title_empty_trash);
builder.setMessage(R.string.alert_message_empty_trash);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
mTrashManager.emptyTrash();
Toast.makeText(TrashActivity.this, R.string.toast_empty_trash_success,
Toast.LENGTH_SHORT).show();
startAsyncTrashQuery();
} catch (TrashException e) {
Toast.makeText(TrashActivity.this, getErrorMessage(e), Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
private long[] getSelectedNoteIds() {
java.util.HashSet<Long> selectedIds = mTrashListAdapter.getSelectedItemIds();
long[] ids = new long[selectedIds.size()];
int i = 0;
for (Long id : selectedIds) {
ids[i++] = id;
}
return ids;
}
private String getErrorMessage(TrashException e) {
switch (e.getErrorType()) {
case INVALID_NOTE_ID:
return getString(R.string.error_note_not_exist);
case NOT_IN_TRASH:
return getString(R.string.toast_restore_failed);
case NOTE_NOT_FOUND:
return getString(R.string.error_note_not_exist);
case DATABASE_ERROR:
return getString(R.string.toast_restore_failed);
default:
return getString(R.string.toast_restore_failed);
}
}
@Override
protected void onResume() {
super.onResume();
startAsyncTrashQuery();
}
@Override
public void onBackPressed() {
super.onBackPressed();
finish();
}
}

@ -0,0 +1,54 @@
/*
* 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.util.Log;
import net.micode.notes.data.TrashManager;
/**
* 广
* <p>
* 便30
* </p>
*/
public class TrashCleanupReceiver extends BroadcastReceiver {
private static final String TAG = "TrashCleanupReceiver";
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Log.d(TAG, "Boot completed, starting trash cleanup");
try {
TrashManager trashManager = TrashManager.getInstance(context);
int deletedCount = trashManager.autoCleanupExpiredNotes();
if (deletedCount > 0) {
Log.d(TAG, "Auto cleanup: deleted " + deletedCount + " expired notes");
} else {
Log.d(TAG, "Auto cleanup: no expired notes found");
}
} catch (Exception e) {
Log.e(TAG, "Auto cleanup failed: " + e.getMessage(), e);
}
}
}
}

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@android:color/holo_blue_dark"
android:gravity="center"
android:text="@string/trash_folder_name"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="visible" />
<ListView
android:id="@+id/trash_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@android:color/transparent"
android:divider="@android:drawable/divider_horizontal_bright"
android:dividerHeight="1dp"
android:scrollbarStyle="outsideOverlay" />
</LinearLayout>
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/trash_empty"
android:textColor="@android:color/darker_gray"
android:textSize="16sp"
android:visibility="gone" />
</FrameLayout>

@ -36,4 +36,8 @@
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"/>
<item
android:id="@+id/menu_trash"
android:title="@string/menu_trash"/>
</menu>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_restore"
android:icon="@android:drawable/ic_menu_revert"
android:showAsAction="ifRoom"
android:title="@string/menu_restore" />
<item
android:id="@+id/menu_permanently_delete"
android:icon="@android:drawable/ic_menu_delete"
android:showAsAction="ifRoom"
android:title="@string/menu_permanently_delete" />
<item
android:id="@+id/menu_empty_trash"
android:icon="@android:drawable/ic_menu_delete"
android:showAsAction="ifRoom"
android:title="@string/menu_empty_trash" />
</menu>

@ -177,5 +177,26 @@
<!-- Case of 0 or 2 or more results. -->
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
</plurals>
<!-- Trash folder strings -->
<string name="trash_folder_name">Trash</string>
<string name="trash_empty">Trash is empty</string>
<string name="menu_restore">Restore</string>
<string name="menu_permanently_delete">Permanently delete</string>
<string name="menu_empty_trash">Empty trash</string>
<string name="menu_trash">Trash</string>
<string name="alert_title_permanently_delete">Permanently delete</string>
<string name="alert_message_permanently_delete">Are you sure you want to permanently delete this note?</string>
<string name="alert_message_permanently_delete_multiple">Are you sure you want to permanently delete %d notes?</string>
<string name="alert_title_empty_trash">Empty trash</string>
<string name="alert_message_empty_trash">Are you sure you want to empty trash? All notes will be permanently deleted.</string>
<string name="toast_restore_success">Note restored</string>
<string name="toast_restore_failed">Failed to restore</string>
<string name="toast_restore_multiple">Restored %d notes</string>
<string name="toast_delete_success">Note permanently deleted</string>
<string name="toast_delete_failed">Failed to delete</string>
<string name="toast_delete_multiple">Permanently deleted %d notes</string>
<string name="toast_empty_trash_success">Trash emptied</string>
<string name="toast_empty_trash_failed">Failed to empty trash</string>
</resources>

Loading…
Cancel
Save