Update NotePad to support copying of an entire note to the clipboard.
Change-Id: Icbda36dcdb98d53395af1570e161dad727146f93
This commit is contained in:
@@ -19,7 +19,10 @@ package com.example.android.notepad;
|
||||
import com.example.android.notepad.NotePad.Notes;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClippedData;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -38,7 +41,9 @@ import android.widget.EditText;
|
||||
/**
|
||||
* A generic activity for editing a note in a database. This can be used
|
||||
* either to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
|
||||
* {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}.
|
||||
* {@link Intent#ACTION_EDIT}, or create a new empty note
|
||||
* {@link Intent#ACTION_INSERT}, or create a new note from the current contents
|
||||
* of the clipboard {@link Intent#ACTION_PASTE}.
|
||||
*/
|
||||
public class NoteEditor extends Activity {
|
||||
private static final String TAG = "Notes";
|
||||
@@ -49,9 +54,12 @@ public class NoteEditor extends Activity {
|
||||
private static final String[] PROJECTION = new String[] {
|
||||
Notes._ID, // 0
|
||||
Notes.NOTE, // 1
|
||||
Notes.TITLE, // 2
|
||||
};
|
||||
/** The index of the note column */
|
||||
private static final int COLUMN_INDEX_NOTE = 1;
|
||||
/** The index of the title column */
|
||||
private static final int COLUMN_INDEX_TITLE = 2;
|
||||
|
||||
// This is our state data that is stored when freezing.
|
||||
private static final String ORIGINAL_CONTENT = "origContent";
|
||||
@@ -64,6 +72,7 @@ public class NoteEditor extends Activity {
|
||||
// The different distinct states the activity can be run in.
|
||||
private static final int STATE_EDIT = 0;
|
||||
private static final int STATE_INSERT = 1;
|
||||
private static final int STATE_PASTE = 2;
|
||||
|
||||
private int mState;
|
||||
private boolean mNoteOnly = false;
|
||||
@@ -118,7 +127,8 @@ public class NoteEditor extends Activity {
|
||||
// Requested to edit: set that state, and the data being edited.
|
||||
mState = STATE_EDIT;
|
||||
mUri = intent.getData();
|
||||
} else if (Intent.ACTION_INSERT.equals(action)) {
|
||||
} else if (Intent.ACTION_INSERT.equals(action)
|
||||
|| Intent.ACTION_PASTE.equals(action)) {
|
||||
// Requested to insert: set that state, and create a new entry
|
||||
// in the container.
|
||||
mState = STATE_INSERT;
|
||||
@@ -137,6 +147,13 @@ public class NoteEditor extends Activity {
|
||||
// set the result to be returned.
|
||||
setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
|
||||
|
||||
// If pasting, initialize data from clipboard.
|
||||
if (Intent.ACTION_PASTE.equals(action)) {
|
||||
performPaste();
|
||||
// Switch to paste mode; can no longer modify title.
|
||||
mState = STATE_PASTE;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Whoops, unknown action! Bail.
|
||||
Log.e(TAG, "Unknown action, exiting");
|
||||
@@ -173,7 +190,7 @@ public class NoteEditor extends Activity {
|
||||
// Modify our overall title depending on the mode we are running in.
|
||||
if (mState == STATE_EDIT) {
|
||||
setTitle(getText(R.string.title_edit));
|
||||
} else if (mState == STATE_INSERT) {
|
||||
} else if (mState == STATE_INSERT || mState == STATE_PASTE) {
|
||||
setTitle(getText(R.string.title_create));
|
||||
}
|
||||
|
||||
@@ -224,34 +241,7 @@ public class NoteEditor extends Activity {
|
||||
|
||||
// Get out updates into the provider.
|
||||
} else {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
// This stuff is only done when working with a full-fledged note.
|
||||
if (!mNoteOnly) {
|
||||
// Bump the modification time to now.
|
||||
values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
|
||||
|
||||
// If we are creating a new note, then we want to also create
|
||||
// an initial title for it.
|
||||
if (mState == STATE_INSERT) {
|
||||
String title = text.substring(0, Math.min(30, length));
|
||||
if (length > 30) {
|
||||
int lastSpace = title.lastIndexOf(' ');
|
||||
if (lastSpace > 0) {
|
||||
title = title.substring(0, lastSpace);
|
||||
}
|
||||
}
|
||||
values.put(Notes.TITLE, title);
|
||||
}
|
||||
}
|
||||
|
||||
// Write our text back into the provider.
|
||||
values.put(Notes.NOTE, text);
|
||||
|
||||
// Commit all of our changes to persistent storage. When the update completes
|
||||
// the content provider will notify the cursor of the change, which will
|
||||
// cause the UI to be updated.
|
||||
getContentResolver().update(mUri, values, null, null);
|
||||
updateNote(text, null, !mNoteOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,6 +301,82 @@ public class NoteEditor extends Activity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
//BEGIN_INCLUDE(paste)
|
||||
/**
|
||||
* Replace the note's data with the current contents of the clipboard.
|
||||
*/
|
||||
private final void performPaste() {
|
||||
ClipboardManager clipboard = (ClipboardManager)
|
||||
getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ContentResolver cr = getContentResolver();
|
||||
|
||||
ClippedData clip = clipboard.getPrimaryClip();
|
||||
if (clip != null) {
|
||||
String text=null, title=null;
|
||||
|
||||
ClippedData.Item item = clip.getItem(0);
|
||||
Uri uri = item.getUri();
|
||||
if (uri != null && NotePad.Notes.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) {
|
||||
// The clipboard holds a reference to a note. Copy it.
|
||||
Cursor orig = cr.query(uri, PROJECTION, null, null, null);
|
||||
if (orig != null) {
|
||||
if (orig.moveToFirst()) {
|
||||
text = orig.getString(COLUMN_INDEX_NOTE);
|
||||
title = orig.getString(COLUMN_INDEX_TITLE);
|
||||
}
|
||||
orig.close();
|
||||
}
|
||||
}
|
||||
|
||||
// If we weren't able to load the clipped data as a note, then
|
||||
// convert whatever it is to text.
|
||||
if (text == null) {
|
||||
text = item.coerceToText(this).toString();
|
||||
}
|
||||
|
||||
updateNote(text, title, true);
|
||||
}
|
||||
}
|
||||
//END_INCLUDE(paste)
|
||||
|
||||
/**
|
||||
* Replace the current note contents with the given data.
|
||||
*/
|
||||
private final void updateNote(String text, String title, boolean updateTitle) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
// This stuff is only done when working with a full-fledged note.
|
||||
if (updateTitle) {
|
||||
// Bump the modification time to now.
|
||||
values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
|
||||
|
||||
// If we are creating a new note, then we want to also create
|
||||
// an initial title for it.
|
||||
if (mState == STATE_INSERT) {
|
||||
if (title == null) {
|
||||
int length = text.length();
|
||||
title = text.substring(0, Math.min(30, length));
|
||||
if (length > 30) {
|
||||
int lastSpace = title.lastIndexOf(' ');
|
||||
if (lastSpace > 0) {
|
||||
title = title.substring(0, lastSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
values.put(Notes.TITLE, title);
|
||||
}
|
||||
}
|
||||
|
||||
// Write our text back into the provider.
|
||||
values.put(Notes.NOTE, text);
|
||||
|
||||
// Commit all of our changes to persistent storage. When the update completes
|
||||
// the content provider will notify the cursor of the change, which will
|
||||
// cause the UI to be updated.
|
||||
getContentResolver().update(mUri, values, null, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Take care of canceling work on a note. Deletes the note if we
|
||||
* had created it, otherwise reverts to the original text.
|
||||
|
||||
@@ -23,6 +23,8 @@ import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.content.ContentProvider.PipeDataWriter;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
@@ -30,17 +32,25 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.LiveFolders;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Provides access to a database of notes. Each note has a title, the note
|
||||
* itself, a creation date and a modified data.
|
||||
*/
|
||||
public class NotePadProvider extends ContentProvider {
|
||||
public class NotePadProvider extends ContentProvider implements PipeDataWriter<Cursor> {
|
||||
|
||||
private static final String TAG = "NotePadProvider";
|
||||
|
||||
@@ -150,6 +160,102 @@ public class NotePadProvider extends ContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
//BEGIN_INCLUDE(stream)
|
||||
/**
|
||||
* Return the types of data streams we can return. Currently we only
|
||||
* support URIs to specific notes, and can convert such a note to a
|
||||
* plain text stream.
|
||||
*/
|
||||
@Override
|
||||
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
|
||||
switch (sUriMatcher.match(uri)) {
|
||||
case NOTES:
|
||||
case LIVE_FOLDER_NOTES:
|
||||
return null;
|
||||
|
||||
case NOTE_ID:
|
||||
if (compareMimeTypes("text/plain", mimeTypeFilter)) {
|
||||
return new String[] { "text/plain" };
|
||||
}
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard projection for the interesting columns of a normal note.
|
||||
*/
|
||||
private static final String[] READ_NOTE_PROJECTION = new String[] {
|
||||
Notes._ID, // 0
|
||||
Notes.NOTE, // 1
|
||||
NotePad.Notes.TITLE, // 2
|
||||
};
|
||||
private static final int READ_NOTE_NOTE_INDEX = 1;
|
||||
private static final int READ_NOTE_TITLE_INDEX = 2;
|
||||
|
||||
/**
|
||||
* Implement the other side of getStreamTypes: for each stream time we
|
||||
* report to support, we need to actually be able to return a stream of
|
||||
* data. This function simply retrieves a cursor for the URI of interest,
|
||||
* and uses ContentProvider's openPipeHelper() to start the work of
|
||||
* convering the data off into another thread.
|
||||
*/
|
||||
@Override
|
||||
public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
|
||||
throws FileNotFoundException {
|
||||
// Check if we support a stream MIME type for this URI.
|
||||
String[] mimeTypes = getStreamTypes(uri, mimeTypeFilter);
|
||||
if (mimeTypes != null) {
|
||||
// Retrieve the note for this URI.
|
||||
Cursor c = query(uri, READ_NOTE_PROJECTION, null, null, null);
|
||||
if (c == null || !c.moveToFirst()) {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
throw new FileNotFoundException("Unable to query " + uri);
|
||||
}
|
||||
// Start a thread to pipe the data back to the client.
|
||||
return new AssetFileDescriptor(
|
||||
openPipeHelper(uri, mimeTypes[0], opts, c, this), 0,
|
||||
AssetFileDescriptor.UNKNOWN_LENGTH);
|
||||
}
|
||||
return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link android.content.ContentProvider.PipeDataWriter}
|
||||
* to perform the actual work of converting the data in one of cursors to a
|
||||
* stream of data for the client to read.
|
||||
*/
|
||||
@Override
|
||||
public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
|
||||
Bundle opts, Cursor c) {
|
||||
// We currently only support conversion-to-text from a single note entry,
|
||||
// so no need for cursor data type checking here.
|
||||
FileOutputStream fout = new FileOutputStream(output.getFileDescriptor());
|
||||
PrintWriter pw = null;
|
||||
try {
|
||||
pw = new PrintWriter(new OutputStreamWriter(fout, "UTF-8"));
|
||||
pw.println(c.getString(READ_NOTE_TITLE_INDEX));
|
||||
pw.println("");
|
||||
pw.println(c.getString(READ_NOTE_NOTE_INDEX));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.w(TAG, "Ooops", e);
|
||||
} finally {
|
||||
c.close();
|
||||
if (pw != null) {
|
||||
pw.flush();
|
||||
}
|
||||
try {
|
||||
fout.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
//END_INCLUDE(stream)
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues initialValues) {
|
||||
// Validate the requested uri
|
||||
|
||||
@@ -19,8 +19,11 @@ package com.example.android.notepad;
|
||||
import com.example.android.notepad.NotePad.Notes;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClippedData;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@@ -45,7 +48,9 @@ public class NotesList extends ListActivity {
|
||||
|
||||
// Menu item ids
|
||||
public static final int MENU_ITEM_DELETE = Menu.FIRST;
|
||||
public static final int MENU_ITEM_INSERT = Menu.FIRST + 1;
|
||||
public static final int MENU_ITEM_COPY = Menu.FIRST + 1;
|
||||
public static final int MENU_ITEM_INSERT = Menu.FIRST + 2;
|
||||
public static final int MENU_ITEM_PASTE = Menu.FIRST + 3;
|
||||
|
||||
/**
|
||||
* The columns we are interested in from the database
|
||||
@@ -58,6 +63,8 @@ public class NotesList extends ListActivity {
|
||||
/** The index of the title column */
|
||||
private static final int COLUMN_INDEX_TITLE = 1;
|
||||
|
||||
private MenuItem mPasteItem;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -95,6 +102,11 @@ public class NotesList extends ListActivity {
|
||||
.setShortcut('3', 'a')
|
||||
.setIcon(android.R.drawable.ic_menu_add);
|
||||
|
||||
// If there is currently data in the clipboard, we can paste it
|
||||
// as a new note.
|
||||
mPasteItem = menu.add(0, MENU_ITEM_PASTE, 0, R.string.menu_paste)
|
||||
.setShortcut('4', 'p');
|
||||
|
||||
// Generate any additional actions that can be performed on the
|
||||
// overall list. In a normal install, there are no additional
|
||||
// actions found here, but this allows other applications to extend
|
||||
@@ -110,6 +122,16 @@ public class NotesList extends ListActivity {
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
|
||||
// The paste menu item is enabled if there is data on the clipboard.
|
||||
ClipboardManager clipboard = (ClipboardManager)
|
||||
getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboard.hasPrimaryClip()) {
|
||||
mPasteItem.setEnabled(true);
|
||||
} else {
|
||||
mPasteItem.setEnabled(false);
|
||||
}
|
||||
|
||||
final boolean haveItems = getListAdapter().getCount() > 0;
|
||||
|
||||
// If there are any notes in the list (which implies that one of
|
||||
@@ -150,6 +172,10 @@ public class NotesList extends ListActivity {
|
||||
// Launch activity to insert a new item
|
||||
startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));
|
||||
return true;
|
||||
case MENU_ITEM_PASTE:
|
||||
// Launch activity to insert a new item
|
||||
startActivity(new Intent(Intent.ACTION_PASTE, getIntent().getData()));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -173,6 +199,9 @@ public class NotesList extends ListActivity {
|
||||
// Setup the menu header
|
||||
menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE));
|
||||
|
||||
// Add a menu item to copy the note
|
||||
menu.add(0, MENU_ITEM_COPY, 0, R.string.menu_copy);
|
||||
|
||||
// Add a menu item to delete the note
|
||||
menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_delete);
|
||||
}
|
||||
@@ -194,6 +223,17 @@ public class NotesList extends ListActivity {
|
||||
getContentResolver().delete(noteUri, null, null);
|
||||
return true;
|
||||
}
|
||||
//BEGIN_INCLUDE(copy)
|
||||
case MENU_ITEM_COPY: {
|
||||
// Copy the note that the context menu is for on to the clipboard
|
||||
ClipboardManager clipboard = (ClipboardManager)
|
||||
getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
|
||||
clipboard.setPrimaryClip(new ClippedData(null, null, new ClippedData.Item(
|
||||
noteUri)));
|
||||
return true;
|
||||
}
|
||||
//END_INCLUDE(copy)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user