diff options
| author | Dianne Hackborn <hackbod@google.com> | 2010-08-06 17:09:29 -0700 |
|---|---|---|
| committer | Dianne Hackborn <hackbod@google.com> | 2010-08-08 18:38:49 -0700 |
| commit | 4779ab6f9aa4d6b691f051e069ffac31475f850a (patch) | |
| tree | afa52d3a1dd3a2210215c4374f11f26ea3a2dd20 /samples/NotePad | |
| parent | 762a14fa3c6ce6691d622305921ed35b9dcf931f (diff) | |
| download | android_development-4779ab6f9aa4d6b691f051e069ffac31475f850a.tar.gz android_development-4779ab6f9aa4d6b691f051e069ffac31475f850a.tar.bz2 android_development-4779ab6f9aa4d6b691f051e069ffac31475f850a.zip | |
Update NotePad to support copying of an entire note to the clipboard.
Change-Id: Icbda36dcdb98d53395af1570e161dad727146f93
Diffstat (limited to 'samples/NotePad')
5 files changed, 251 insertions, 34 deletions
diff --git a/samples/NotePad/AndroidManifest.xml b/samples/NotePad/AndroidManifest.xml index 04f4dbee5..7d41dd52e 100644 --- a/samples/NotePad/AndroidManifest.xml +++ b/samples/NotePad/AndroidManifest.xml @@ -65,9 +65,12 @@ </intent-filter> <!-- This filter says that we can create a new note inside - of a directory of notes. --> + of a directory of notes. The INSERT action creates an + empty note; the PASTE action initializes a new note from + the current contents of the clipboard. --> <intent-filter> <action android:name="android.intent.action.INSERT" /> + <action android:name="android.intent.action.PASTE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> </intent-filter> diff --git a/samples/NotePad/res/values/strings.xml b/samples/NotePad/res/values/strings.xml index 168db92be..43be4dde7 100644 --- a/samples/NotePad/res/values/strings.xml +++ b/samples/NotePad/res/values/strings.xml @@ -15,8 +15,10 @@ --> <resources> + <string name="menu_copy">Copy</string> <string name="menu_delete">Delete</string> <string name="menu_insert">Add note</string> + <string name="menu_paste">Paste</string> <string name="menu_revert">Revert</string> <string name="menu_discard">Discard</string> diff --git a/samples/NotePad/src/com/example/android/notepad/NoteEditor.java b/samples/NotePad/src/com/example/android/notepad/NoteEditor.java index e45efd865..57b464678 100644 --- a/samples/NotePad/src/com/example/android/notepad/NoteEditor.java +++ b/samples/NotePad/src/com/example/android/notepad/NoteEditor.java @@ -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. diff --git a/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java b/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java index 58cdc8fd5..d79be2427 100644 --- a/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java +++ b/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java @@ -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 diff --git a/samples/NotePad/src/com/example/android/notepad/NotesList.java b/samples/NotePad/src/com/example/android/notepad/NotesList.java index ceaaa3c76..bbcb936a3 100644 --- a/samples/NotePad/src/com/example/android/notepad/NotesList.java +++ b/samples/NotePad/src/com/example/android/notepad/NotesList.java @@ -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; } |
