Note Editor
//
//src\com\example\android\notepad\NoteEditor.java
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 com.example.android.notepad;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.EditText;
/**
* This Activity handles "editing" a note, where editing is responding to
* {@link Intent#ACTION_VIEW} (request to view data), edit a note
* {@link Intent#ACTION_EDIT}, create a note {@link Intent#ACTION_INSERT}, or
* create a new note from the current contents of the clipboard {@link Intent#ACTION_PASTE}.
*
* NOTE: Notice that the provider operations in this Activity are taking place on the UI thread.
* This is not a good practice. It is only done here to make the code more readable. A real
* application should use the {@link android.content.AsyncQueryHandler}
* or {@link android.os.AsyncTask} object to perform operations asynchronously on a separate thread.
*/
public class NoteEditor extends Activity {
// For logging and debugging purposes
private static final String TAG = "NoteEditor";
/*
* Creates a projection that returns the note ID and the note contents.
*/
private static final String[] PROJECTION =
new String[] {
NotePad.Notes._ID,
NotePad.Notes.COLUMN_NAME_TITLE,
NotePad.Notes.COLUMN_NAME_NOTE
};
// A label for the saved state of the activity
private static final String ORIGINAL_CONTENT = "origContent";
// This Activity can be started by more than one action. Each action is represented
// as a "state" constant
private static final int STATE_EDIT = 0;
private static final int STATE_INSERT = 1;
// Global mutable variables
private int mState;
private Uri mUri;
private Cursor mCursor;
private EditText mText;
private String mOriginalContent;
/**
* Defines a custom EditText View that draws lines between each line of text that is displayed.
*/
public static class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
// This constructor is used by LayoutInflater
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
// Creates a Rect and a Paint object, and sets the style and color of the Paint object.
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
/**
* This is called to draw the LinedEditText object
* @param canvas The canvas on which the background is drawn.
*/
@Override
protected void onDraw(Canvas canvas) {
// Gets the number of lines of text in the View.
int count = getLineCount();
// Gets the global Rect and Paint objects
Rect r = mRect;
Paint paint = mPaint;
/*
* Draws one line in the rectangle for every line of text in the EditText
*/
for (int i = 0; i < count; i++) {
// Gets the baseline coordinates for the current line of text
int baseline = getLineBounds(i, r);
/*
* Draws a line in the background from the left of the rectangle to the right,
* at a vertical position one dip below the baseline, using the "paint" object
* for details.
*/
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
}
// Finishes up by calling the parent method
super.onDraw(canvas);
}
}
/**
* This method is called by Android when the Activity is first started. From the incoming
* Intent, it determines what kind of editing is desired, and then does it.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Creates an Intent to use when the Activity object's result is sent back to the
* caller.
*/
final Intent intent = getIntent();
/*
* Sets up for the edit, based on the action specified for the incoming Intent.
*/
// Gets the action that triggered the intent filter for this Activity
final String action = intent.getAction();
// For an edit action:
if (Intent.ACTION_EDIT.equals(action)) {
// Sets the Activity state to EDIT, and gets the URI for the data to be edited.
mState = STATE_EDIT;
mUri = intent.getData();
// For an insert or paste action:
} else if (Intent.ACTION_INSERT.equals(action)
|| Intent.ACTION_PASTE.equals(action)) {
// Sets the Activity state to INSERT, gets the general note URI, and inserts an
// empty record in the provider
mState = STATE_INSERT;
mUri = getContentResolver().insert(intent.getData(), null);
/*
* If the attempt to insert the new note fails, shuts down this Activity. The
* originating Activity receives back RESULT_CANCELED if it requested a result.
* Logs that the insert failed.
*/
if (mUri == null) {
// Writes the log identifier, a message, and the URI that failed.
Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
// Closes the activity.
finish();
return;
}
// Since the new entry was created, this sets the result to be returned
// set the result to be returned.
setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
// If the action was other than EDIT or INSERT:
} else {
// Logs an error that the action was not understood, finishes the Activity, and
// returns RESULT_CANCELED to an originating Activity.
Log.e(TAG, "Unknown action, exiting");
finish();
return;
}
/*
* Using the URI passed in with the triggering Intent, gets the note or notes in
* the provider.
* Note: This is being done on the UI thread. It will block the thread until the query
* completes. In a sample app, going against a simple provider based on a local database,
* the block will be momentary, but in a real app you should use
* android.content.AsyncQueryHandler or android.os.AsyncTask.
*/
mCursor = managedQuery(
mUri, // The URI that gets multiple notes from the provider.
PROJECTION, // A projection that returns the note ID and note content for each note.
null, // No "where" clause selection criteria.
null, // No "where" clause selection values.
null // Use the default sort order (modification date, descending)
);
// For a paste, initializes the data from clipboard.
// (Must be done after mCursor is initialized.)
if (Intent.ACTION_PASTE.equals(action)) {
// Does the paste
performPaste();
// Switches the state to EDIT so the title can be modified.
mState = STATE_EDIT;
}
// Sets the layout for this Activity. See res/layout/note_editor.xml
setContentView(R.layout.note_editor);
// Gets a handle to the EditText in the the layout.
mText = (EditText) findViewById(R.id.note);
/*
* If this Activity had stopped previously, its state was written the ORIGINAL_CONTENT
* location in the saved Instance state. This gets the state.
*/
if (savedInstanceState != null) {
mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
}
}
/**
* This method is called when the Activity is about to come to the foreground. This happens
* when the Activity comes to the top of the task stack, OR when it is first starting.
*
* Moves to the first note in the list, sets an appropriate title for the action chosen by
* the user, puts the note contents into the TextView, and saves the original text as a
* backup.
*/
@Override
protected void onResume() {
super.onResume();
/*
* mCursor is initialized, since onCreate() always precedes onResume for any running
* process. This tests that it's not null, since it should always contain data.
*/
if (mCursor != null) {
// Requery in case something changed while paused (such as the title)
mCursor.requery();
/* Moves to the first record. Always call moveToFirst() before accessing data in
* a Cursor for the first time. The semantics of using a Cursor are that when it is
* created, its internal index is pointing to a "place" immediately before the first
* record.
*/
mCursor.moveToFirst();
// Modifies the window title for the Activity according to the current Activity state.
if (mState == STATE_EDIT) {
// Set the title of the Activity to include the note title
int colTitleIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
String title = mCursor.getString(colTitleIndex);
Resources res = getResources();
String text = String.format(res.getString(R.string.title_edit), title);
setTitle(text);
// Sets the title to "create" for inserts
} else if (mState == STATE_INSERT) {
setTitle(getText(R.string.title_create));
}
/*
* onResume() may have been called after the Activity lost focus (was paused).
* The user was either editing or creating a note when the Activity paused.
* The Activity should re-display the text that had been retrieved previously, but
* it should not move the cursor. This helps the user to continue editing or entering.
*/
// Gets the note text from the Cursor and puts it in the TextView, but doesn't change
// the text cursor's position.
int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
String note = mCursor.getString(colNoteIndex);
mText.setTextKeepState(note);
// Stores the original note text, to allow the user to revert changes.
if (mOriginalContent == null) {
mOriginalContent = note;
}
/*
* Something is wrong. The Cursor should always contain data. Report an error in the
* note.
*/
} else {
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
/**
* This method is called when an Activity loses focus during its normal operation, and is then
* later on killed. The Activity has a chance to save its state so that the system can restore
* it.
*
* Notice that this method isn't a normal part of the Activity lifecycle. It won't be called
* if the user simply navigates away from the Activity.
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save away the original text, so we still have it if the activity
// needs to be killed while paused.
outState.putString(ORIGINAL_CONTENT, mOriginalContent);
}
/**
* This method is called when the Activity loses focus.
*
* For Activity objects that edit information, onPause() may be the one place where changes are
* saved. The Android application model is predicated on the idea that "save" and "exit" aren't
* required actions. When users navigate away from an Activity, they shouldn't have to go back
* to it to complete their work. The act of going away should save everything and leave the
* Activity in a state where Android can destroy it if necessary.
*
* If the user hasn't done anything, then this deletes or clears out the note, otherwise it
* writes the user's work to the provider.
*/
@Override
protected void onPause() {
super.onPause();
/*
* Tests to see that the query operation didn't fail (see onCreate()). The Cursor object
* will exist, even if no records were returned, unless the query failed because of some
* exception or error.
*
*/
if (mCursor != null) {
// Get the current note text.
String text = mText.getText().toString();
int length = text.length();
/*
* If the Activity is in the midst of finishing and there is no text in the current
* note, returns a result of CANCELED to the caller, and deletes the note. This is done
* even if the note was being edited, the assumption being that the user wanted to
* "clear out" (delete) the note.
*/
if (isFinishing() && (length == 0)) {
setResult(RESULT_CANCELED);
deleteNote();
/*
* Writes the edits to the provider. The note has been edited if an existing note was
* retrieved into the editor *or* if a new note was inserted. In the latter case,
* onCreate() inserted a new empty note into the provider, and it is this new note
* that is being edited.
*/
} else if (mState == STATE_EDIT) {
// Creates a map to contain the new values for the columns
updateNote(text, null);
} else if (mState == STATE_INSERT) {
updateNote(text, text);
mState = STATE_EDIT;
}
}
}
/**
* This method is called when the user clicks the device's Menu button the first time for
* this Activity. Android passes in a Menu object that is populated with items.
*
* Builds the menus for editing and inserting, and adds in alternative actions that
* registered themselves to handle the MIME types for this application.
*
* @param menu A Menu object to which items should be added.
* @return True to display the menu.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate menu from XML resource
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.editor_options_menu, menu);
// Only add extra menu items for a saved note
if (mState == STATE_EDIT) {
// Append to the
// menu items for any other activities that can do stuff with it
// as well. This does a query on the system for any activities that
// implement the ALTERNATIVE_ACTION for our data, adding a menu item
// for each one that is found.
Intent intent = new Intent(null, mUri);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, NoteEditor.class), null, intent, 0, null);
}
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Check if note has changed and enable/disable the revert option
int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
String savedNote = mCursor.getString(colNoteIndex);
String currentNote = mText.getText().toString();
if (savedNote.equals(currentNote)) {
menu.findItem(R.id.menu_revert).setVisible(false);
} else {
menu.findItem(R.id.menu_revert).setVisible(true);
}
return super.onPrepareOptionsMenu(menu);
}
/**
* This method is called when a menu item is selected. Android passes in the selected item.
* The switch statement in this method calls the appropriate method to perform the action the
* user chose.
*
* @param item The selected MenuItem
* @return True to indicate that the item was processed, and no further work is necessary. False
* to proceed to further processing as indicated in the MenuItem object.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle all of the possible menu actions.
switch (item.getItemId()) {
case R.id.menu_save:
String text = mText.getText().toString();
updateNote(text, null);
finish();
break;
case R.id.menu_delete:
deleteNote();
finish();
break;
case R.id.menu_revert:
cancelNote();
break;
}
return super.onOptionsItemSelected(item);
}
/**
* A helper method that replaces the note's data with the contents of the clipboard.
*/
private final void performPaste() {
// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
// Gets a content resolver instance
ContentResolver cr = getContentResolver();
// Gets the clipboard data from the clipboard
ClipData clip = clipboard.getPrimaryClip();
if (clip != null) {
String text=null;
String title=null;
// Gets the first item from the clipboard data
ClipData.Item item = clip.getItemAt(0);
// Tries to get the item's contents as a URI pointing to a note
Uri uri = item.getUri();
// Tests to see that the item actually is an URI, and that the URI
// is a content URI pointing to a provider whose MIME type is the same
// as the MIME type supported by the Note pad provider.
if (uri != null && NotePad.Notes.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) {
// The clipboard holds a reference to data with a note MIME type. This copies it.
Cursor orig = cr.query(
uri, // URI for the content provider
PROJECTION, // Get the columns referred to in the projection
null, // No selection variables
null, // No selection variables, so no criteria are needed
null // Use the default sort order
);
// If the Cursor is not null, and it contains at least one record
// (moveToFirst() returns true), then this gets the note data from it.
if (orig != null) {
if (orig.moveToFirst()) {
int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
int colTitleIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
text = orig.getString(colNoteIndex);
title = orig.getString(colTitleIndex);
}
// Closes the cursor.
orig.close();
}
}
// If the contents of the clipboard wasn't a reference to a note, then
// this converts whatever it is to text.
if (text == null) {
text = item.coerceToText(this).toString();
}
// Updates the current note with the retrieved title and text.
updateNote(text, title);
}
}
/**
* Replaces the current note contents with the text and title provided as arguments.
* @param text The new note contents to use.
* @param title The new note title to use
*/
private final void updateNote(String text, String title) {
// Sets up a map to contain values to be updated in the provider.
ContentValues values = new ContentValues();
values.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, System.currentTimeMillis());
// If the action is to insert a new note, this creates an initial title for it.
if (mState == STATE_INSERT) {
// If no title was provided as an argument, create one from the note text.
if (title == null) {
// Get the note's length
int length = text.length();
// Sets the title by getting a substring of the text that is 31 characters long
// or the number of characters in the note plus one, whichever is smaller.
title = text.substring(0, Math.min(30, length));
// If the resulting length is more than 30 characters, chops off any
// trailing spaces
if (length > 30) {
int lastSpace = title.lastIndexOf(' ');
if (lastSpace > 0) {
title = title.substring(0, lastSpace);
}
}
}
// In the values map, sets the value of the title
values.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
} else if (title != null) {
// In the values map, sets the value of the title
values.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
}
// This puts the desired notes text into the map.
values.put(NotePad.Notes.COLUMN_NAME_NOTE, text);
/*
* Updates the provider with the new values in the map. The ListView is updated
* automatically. The provider sets this up by setting the notification URI for
* query Cursor objects to the incoming URI. The content resolver is thus
* automatically notified when the Cursor for the URI changes, and the UI is
* updated.
* Note: This is being done on the UI thread. It will block the thread until the
* update completes. In a sample app, going against a simple provider based on a
* local database, the block will be momentary, but in a real app you should use
* android.content.AsyncQueryHandler or android.os.AsyncTask.
*/
getContentResolver().update(
mUri, // The URI for the record to update.
values, // The map of column names and new values to apply to them.
null, // No selection criteria are used, so no where columns are necessary.
null // No where columns are used, so no where arguments are necessary.
);
}
/**
* This helper method cancels the work done on a note. It deletes the note if it was
* newly created, or reverts to the original text of the note i
*/
private final void cancelNote() {
if (mCursor != null) {
if (mState == STATE_EDIT) {
// Put the original note text back into the database
mCursor.close();
mCursor = null;
ContentValues values = new ContentValues();
values.put(NotePad.Notes.COLUMN_NAME_NOTE, mOriginalContent);
getContentResolver().update(mUri, values, null, null);
} else if (mState == STATE_INSERT) {
// We inserted an empty note, make sure to delete it
deleteNote();
}
}
setResult(RESULT_CANCELED);
finish();
}
/**
* Take care of deleting a note. Simply deletes the entry.
*/
private final void deleteNote() {
if (mCursor != null) {
mCursor.close();
mCursor = null;
getContentResolver().delete(mUri, null, null);
mText.setText("");
}
}
}
//src\com\example\android\notepad\NotePad.java
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 com.example.android.notepad;
import android.net.Uri;
import android.provider.BaseColumns;
/**
* Defines a contract between the Note Pad content provider and its clients. A contract defines the
* information that a client needs to access the provider as one or more data tables. A contract
* is a public, non-extendable (final) class that contains constants defining column names and
* URIs. A well-written client depends only on the constants in the contract.
*/
public final class NotePad {
public static final String AUTHORITY = "com.google.provider.NotePad";
// This class cannot be instantiated
private NotePad() {
}
/**
* Notes table contract
*/
public static final class Notes implements BaseColumns {
// This class cannot be instantiated
private Notes() {}
/**
* The table name offered by this provider
*/
public static final String TABLE_NAME = "notes";
/*
* URI definitions
*/
/**
* The scheme part for this provider's URI
*/
private static final String SCHEME = "content://";
/**
* Path parts for the URIs
*/
/**
* Path part for the Notes URI
*/
private static final String PATH_NOTES = "/notes";
/**
* Path part for the Note ID URI
*/
private static final String PATH_NOTE_ID = "/notes/";
/**
* 0-relative position of a note ID segment in the path part of a note ID URI
*/
public static final int NOTE_ID_PATH_POSITION = 1;
/**
* Path part for the Live Folder URI
*/
private static final String PATH_LIVE_FOLDER = "/live_folders/notes";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse(SCHEME + AUTHORITY + PATH_NOTES);
/**
* The content URI base for a single note. Callers must
* append a numeric note id to this Uri to retrieve a note
*/
public static final Uri CONTENT_ID_URI_BASE
= Uri.parse(SCHEME + AUTHORITY + PATH_NOTE_ID);
/**
* The content URI match pattern for a single note, specified by its ID. Use this to match
* incoming URIs or to construct an Intent.
*/
public static final Uri CONTENT_ID_URI_PATTERN
= Uri.parse(SCHEME + AUTHORITY + PATH_NOTE_ID + "/#");
/**
* The content Uri pattern for a notes listing for live folders
*/
public static final Uri LIVE_FOLDER_URI
= Uri.parse(SCHEME + AUTHORITY + PATH_LIVE_FOLDER);
/*
* MIME type definitions
*/
/**
* The MIME type of {@link #CONTENT_URI} providing a directory of notes.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.note";
/**
* The MIME type of a {@link #CONTENT_URI} sub-directory of a single
* note.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.note";
/**
* The default sort order for this table
*/
public static final String DEFAULT_SORT_ORDER = "modified DESC";
/*
* Column definitions
*/
/**
* Column name for the title of the note
* <P>Type: TEXT</P>
*/
public static final String COLUMN_NAME_TITLE = "title";
/**
* Column name of the note content
* <P>Type: TEXT</P>
*/
public static final String COLUMN_NAME_NOTE = "note";
/**
* Column name for the creation timestamp
* <P>Type: INTEGER (long from System.curentTimeMillis())</P>
*/
public static final String COLUMN_NAME_CREATE_DATE = "created";
/**
* Column name for the modification timestamp
* <P>Type: INTEGER (long from System.curentTimeMillis())</P>
*/
public static final String COLUMN_NAME_MODIFICATION_DATE = "modified";
}
}
//src\com\example\android\notepad\NotePadProvider.java
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 com.example.android.notepad;
import com.example.android.notepad.NotePad;
import android.content.ClipDescription;
import android.content.ContentProvider;
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;
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 implements PipeDataWriter<Cursor> {
// Used for debugging and logging
private static final String TAG = "NotePadProvider";
/**
* The database that the provider uses as its underlying data store
*/
private static final String DATABASE_NAME = "note_pad.db";
/**
* The database version
*/
private static final int DATABASE_VERSION = 2;
/**
* A projection map used to select columns from the database
*/
private static HashMap<String, String> sNotesProjectionMap;
/**
* A projection map used to select columns from the database
*/
private static HashMap<String, String> sLiveFolderProjectionMap;
/**
* Standard projection for the interesting columns of a normal note.
*/
private static final String[] READ_NOTE_PROJECTION = new String[] {
NotePad.Notes._ID, // Projection position 0, the note's id
NotePad.Notes.COLUMN_NAME_NOTE, // Projection position 1, the note's content
NotePad.Notes.COLUMN_NAME_TITLE, // Projection position 2, the note's title
};
private static final int READ_NOTE_NOTE_INDEX = 1;
private static final int READ_NOTE_TITLE_INDEX = 2;
/*
* Constants used by the Uri matcher to choose an action based on the pattern
* of the incoming URI
*/
// The incoming URI matches the Notes URI pattern
private static final int NOTES = 1;
// The incoming URI matches the Note ID URI pattern
private static final int NOTE_ID = 2;
// The incoming URI matches the Live Folder URI pattern
private static final int LIVE_FOLDER_NOTES = 3;
/**
* A UriMatcher instance
*/
private static final UriMatcher sUriMatcher;
// Handle to a new DatabaseHelper.
private DatabaseHelper mOpenHelper;
/**
* A block that instantiates and sets static objects
*/
static {
/*
* Creates and initializes the URI matcher
*/
// Create a new instance
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add a pattern that routes URIs terminated with "notes" to a NOTES operation
sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES);
// Add a pattern that routes URIs terminated with "notes" plus an integer
// to a note ID operation
sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID);
// Add a pattern that routes URIs terminated with live_folders/notes to a
// live folder operation
sUriMatcher.addURI(NotePad.AUTHORITY, "live_folders/notes", LIVE_FOLDER_NOTES);
/*
* Creates and initializes a projection map that returns all columns
*/
// Creates a new projection map instance. The map returns a column name
// given a string. The two are usually equal.
sNotesProjectionMap = new HashMap<String, String>();
// Maps the string "_ID" to the column name "_ID"
sNotesProjectionMap.put(NotePad.Notes._ID, NotePad.Notes._ID);
// Maps "title" to "title"
sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_TITLE, NotePad.Notes.COLUMN_NAME_TITLE);
// Maps "note" to "note"
sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_NOTE, NotePad.Notes.COLUMN_NAME_NOTE);
// Maps "created" to "created"
sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE,
NotePad.Notes.COLUMN_NAME_CREATE_DATE);
// Maps "modified" to "modified"
sNotesProjectionMap.put(
NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE,
NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE);
/*
* Creates an initializes a projection map for handling Live Folders
*/
// Creates a new projection map instance
sLiveFolderProjectionMap = new HashMap<String, String>();
// Maps "_ID" to "_ID AS _ID" for a live folder
sLiveFolderProjectionMap.put(LiveFolders._ID, NotePad.Notes._ID + " AS " + LiveFolders._ID);
// Maps "NAME" to "title AS NAME"
sLiveFolderProjectionMap.put(LiveFolders.NAME, NotePad.Notes.COLUMN_NAME_TITLE + " AS " +
LiveFolders.NAME);
}
/**
*
* This class helps open, create, and upgrade the database file. Set to package visibility
* for testing purposes.
*/
static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
// calls the super constructor, requesting the default cursor factory.
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
*
* Creates the underlying database with table name and column names taken from the
* NotePad class.
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + NotePad.Notes.TABLE_NAME + " ("
+ NotePad.Notes._ID + " INTEGER PRIMARY KEY,"
+ NotePad.Notes.COLUMN_NAME_TITLE + " TEXT,"
+ NotePad.Notes.COLUMN_NAME_NOTE + " TEXT,"
+ NotePad.Notes.COLUMN_NAME_CREATE_DATE + " INTEGER,"
+ NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE + " INTEGER"
+ ");");
}
/**
*
* Demonstrates that the provider must consider what happens when the
* underlying datastore is changed. In this sample, the database is upgraded the database
* by destroying the existing data.
* A real application should upgrade the database in place.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Logs that the database is being upgraded
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
// Kills the table and existing data
db.execSQL("DROP TABLE IF EXISTS notes");
// Recreates the database with a new version
onCreate(db);
}
}
/**
*
* Initializes the provider by creating a new DatabaseHelper. onCreate() is called
* automatically when Android creates the provider in response to a resolver request from a
* client.
*/
@Override
public boolean onCreate() {
// Creates a new helper object. Note that the database itself isn't opened until
// something tries to access it, and it's only created if it doesn't already exist.
mOpenHelper = new DatabaseHelper(getContext());
// Assumes that any failures will be reported by a thrown exception.
return true;
}
/**
* This method is called when a client calls
* {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)}.
* Queries the database and returns a cursor containing the results.
*
* @return A cursor containing the results of the query. The cursor exists but is empty if
* the query returns no results or an exception occurs.
* @throws IllegalArgumentException if the incoming URI pattern is invalid.
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Constructs a new query builder and sets its table name
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(NotePad.Notes.TABLE_NAME);
/**
* Choose the projection and adjust the "where" clause based on URI pattern-matching.
*/
switch (sUriMatcher.match(uri)) {
// If the incoming URI is for notes, chooses the Notes projection
case NOTES:
qb.setProjectionMap(sNotesProjectionMap);
break;
/* If the incoming URI is for a single note identified by its ID, chooses the
* note ID projection, and appends "_ID = <noteID>" to the where clause, so that
* it selects that single note
*/
case NOTE_ID:
qb.setProjectionMap(sNotesProjectionMap);
qb.appendWhere(
NotePad.Notes._ID + // the name of the ID column
"=" +
// the position of the note ID itself in the incoming URI
uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION));
break;
case LIVE_FOLDER_NOTES:
// If the incoming URI is from a live folder, chooses the live folder projection.
qb.setProjectionMap(sLiveFolderProjectionMap);
break;
default:
// If the URI doesn't match any of the known patterns, throw an exception.
throw new IllegalArgumentException("Unknown URI " + uri);
}
String orderBy;
// If no sort order is specified, uses the default
if (TextUtils.isEmpty(sortOrder)) {
orderBy = NotePad.Notes.DEFAULT_SORT_ORDER;
} else {
// otherwise, uses the incoming sort order
orderBy = sortOrder;
}
// Opens the database object in "read" mode, since no writes need to be done.
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
/*
* Performs the query. If no problems occur trying to read the database, then a Cursor
* object is returned; otherwise, the cursor variable contains null. If no records were
* selected, then the Cursor object is empty, and Cursor.getCount() returns 0.
*/
Cursor c = qb.query(
db, // The database to query
projection, // The columns to return from the query
selection, // The columns for the where clause
selectionArgs, // The values for the where clause
null, // don't group the rows
null, // don't filter by row groups
orderBy // The sort order
);
// Tells the Cursor what URI to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
/**
* This is called when a client calls {@link android.content.ContentResolver#getType(Uri)}.
* Returns the MIME data type of the URI given as a parameter.
*
* @param uri The URI whose MIME type is desired.
* @return The MIME type of the URI.
* @throws IllegalArgumentException if the incoming URI pattern is invalid.
*/
@Override
public String getType(Uri uri) {
/**
* Chooses the MIME type based on the incoming URI pattern
*/
switch (sUriMatcher.match(uri)) {
// If the pattern is for notes or live folders, returns the general content type.
case NOTES:
case LIVE_FOLDER_NOTES:
return NotePad.Notes.CONTENT_TYPE;
// If the pattern is for note IDs, returns the note ID content type.
case NOTE_ID:
return NotePad.Notes.CONTENT_ITEM_TYPE;
// If the URI pattern doesn't match any permitted patterns, throws an exception.
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
/**
* This describes the MIME types that are supported for opening a note
* URI as a stream.
*/
static ClipDescription NOTE_STREAM_TYPES = new ClipDescription(null,
new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN });
/**
* Returns the types of available data streams. URIs to specific notes are supported.
* The application can convert such a note to a plain text stream.
*
* @param uri the URI to analyze
* @param mimeTypeFilter The MIME type to check for. This method only returns a data stream
* type for MIME types that match the filter. Currently, only text/plain MIME types match.
* @return a data stream MIME type. Currently, only text/plan is returned.
* @throws IllegalArgumentException if the URI pattern doesn't match any supported patterns.
*/
@Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
/**
* Chooses the data stream type based on the incoming URI pattern.
*/
switch (sUriMatcher.match(uri)) {
// If the pattern is for notes or live folders, return null. Data streams are not
// supported for this type of URI.
case NOTES:
case LIVE_FOLDER_NOTES:
return null;
// If the pattern is for note IDs and the MIME filter is text/plain, then return
// text/plain
case NOTE_ID:
return NOTE_STREAM_TYPES.filterMimeTypes(mimeTypeFilter);
// If the URI pattern doesn't match any permitted patterns, throws an exception.
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
/**
* Returns a stream of data for each supported stream type. This method does a query on the
* incoming URI, then uses
* {@link android.content.ContentProvider#openPipeHelper(Uri, String, Bundle, Object,
* PipeDataWriter)} to start another thread in which to convert the data into a stream.
*
* @param uri The URI pattern that points to the data stream
* @param mimeTypeFilter A String containing a MIME type. This method tries to get a stream of
* data with this MIME type.
* @param opts Additional options supplied by the caller. Can be interpreted as
* desired by the content provider.
* @return AssetFileDescriptor A handle to the file.
* @throws FileNotFoundException if there is no file associated with the incoming URI.
*/
@Override
public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
throws FileNotFoundException {
// Checks to see if the MIME type filter matches a supported MIME type.
String[] mimeTypes = getStreamTypes(uri, mimeTypeFilter);
// If the MIME type is supported
if (mimeTypes != null) {
// Retrieves the note for this URI. Uses the query method defined for this provider,
// rather than using the database query method.
Cursor c = query(
uri, // The URI of a note
READ_NOTE_PROJECTION, // Gets a projection containing the note's ID, title,
// and contents
null, // No WHERE clause, get all matching records
null, // Since there is no WHERE clause, no selection criteria
null // Use the default sort order (modification date,
// descending
);
// If the query fails or the cursor is empty, stop
if (c == null || !c.moveToFirst()) {
// If the cursor is empty, simply close the cursor and return
if (c != null) {
c.close();
}
// If the cursor is null, throw an exception
throw new FileNotFoundException("Unable to query " + uri);
}
// Start a new thread that pipes the stream data back to the caller.
return new AssetFileDescriptor(
openPipeHelper(uri, mimeTypes[0], opts, c, this), 0,
AssetFileDescriptor.UNKNOWN_LENGTH);
}
// If the MIME type is not supported, return a read-only handle to the file.
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) {
}
}
}
/**
* This is called when a client calls
* {@link android.content.ContentResolver#insert(Uri, ContentValues)}.
* Inserts a new row into the database. This method sets up default values for any
* columns that are not included in the incoming map.
* If rows were inserted, then listeners are notified of the change.
* @return The row ID of the inserted row.
* @throws SQLException if the insertion fails.
*/
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
// Validates the incoming URI. Only the full provider URI is allowed for inserts.
if (sUriMatcher.match(uri) != NOTES) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
// A map to hold the new record's values.
ContentValues values;
// If the incoming values map is not null, uses it for the new values.
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
// Otherwise, create a new value map
values = new ContentValues();
}
// Gets the current system time in milliseconds
Long now = Long.valueOf(System.currentTimeMillis());
// If the values map doesn't contain the creation date, sets the value to the current time.
if (values.containsKey(NotePad.Notes.COLUMN_NAME_CREATE_DATE) == false) {
values.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, now);
}
// If the values map doesn't contain the modification date, sets the value to the current
// time.
if (values.containsKey(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE) == false) {
values.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, now);
}
// If the values map doesn't contain a title, sets the value to the default title.
if (values.containsKey(NotePad.Notes.COLUMN_NAME_TITLE) == false) {
Resources r = Resources.getSystem();
values.put(NotePad.Notes.COLUMN_NAME_TITLE, r.getString(android.R.string.untitled));
}
// If the values map doesn't contain note text, sets the value to an empty string.
if (values.containsKey(NotePad.Notes.COLUMN_NAME_NOTE) == false) {
values.put(NotePad.Notes.COLUMN_NAME_NOTE, "");
}
// Opens the database object in "write" mode.
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
// Performs the insert and returns the ID of the new note.
long rowId = db.insert(
NotePad.Notes.TABLE_NAME, // The table to insert into.
NotePad.Notes.COLUMN_NAME_NOTE, // A hack, SQLite sets this column value to null
// if values is empty.
values // A map of column names, and the values to insert
// into the columns.
);
// If the insert succeeded, the row ID exists.
if (rowId > 0) {
// Creates a URI with the note ID pattern and the new row ID appended to it.
Uri noteUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, rowId);
// Notifies observers registered against this provider that the data changed.
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
// If the insert didn't succeed, then the rowID is <= 0. Throws an exception.
throw new SQLException("Failed to insert row into " + uri);
}
/**
* This is called when a client calls
* {@link android.content.ContentResolver#delete(Uri, String, String[])}.
* Deletes records from the database. If the incoming URI matches the note ID URI pattern,
* this method deletes the one record specified by the ID in the URI. Otherwise, it deletes a
* a set of records. The record or records must also match the input selection criteria
* specified by where and whereArgs.
*
* If rows were deleted, then listeners are notified of the change.
* @return If a "where" clause is used, the number of rows affected is returned, otherwise
* 0 is returned. To delete all rows and get a row count, use "1" as the where clause.
* @throws IllegalArgumentException if the incoming URI pattern is invalid.
*/
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
// Opens the database object in "write" mode.
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String finalWhere;
int count;
// Does the delete based on the incoming URI pattern.
switch (sUriMatcher.match(uri)) {
// If the incoming pattern matches the general pattern for notes, does a delete
// based on the incoming "where" columns and arguments.
case NOTES:
count = db.delete(
NotePad.Notes.TABLE_NAME, // The database table name
where, // The incoming where clause column names
whereArgs // The incoming where clause values
);
break;
// If the incoming URI matches a single note ID, does the delete based on the
// incoming data, but modifies the where clause to restrict it to the
// particular note ID.
case NOTE_ID:
/*
* Starts a final WHERE clause by restricting it to the
* desired note ID.
*/
finalWhere =
NotePad.Notes._ID + // The ID column name
" = " + // test for equality
uri.getPathSegments(). // the incoming note ID
get(NotePad.Notes.NOTE_ID_PATH_POSITION)
;
// If there were additional selection criteria, append them to the final
// WHERE clause
if (where != null) {
finalWhere = finalWhere + " AND " + where;
}
// Performs the delete.
count = db.delete(
NotePad.Notes.TABLE_NAME, // The database table name.
finalWhere, // The final WHERE clause
whereArgs // The incoming where clause values.
);
break;
// If the incoming pattern is invalid, throws an exception.
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
/*Gets a handle to the content resolver object for the current context, and notifies it
* that the incoming URI changed. The object passes this along to the resolver framework,
* and observers that have registered themselves for the provider are notified.
*/
getContext().getContentResolver().notifyChange(uri, null);
// Returns the number of rows deleted.
return count;
}
/**
* This is called when a client calls
* {@link android.content.ContentResolver#update(Uri,ContentValues,String,String[])}
* Updates records in the database. The column names specified by the keys in the values map
* are updated with new data specified by the values in the map. If the incoming URI matches the
* note ID URI pattern, then the method updates the one record specified by the ID in the URI;
* otherwise, it updates a set of records. The record or records must match the input
* selection criteria specified by where and whereArgs.
* If rows were updated, then listeners are notified of the change.
*
* @param uri The URI pattern to match and update.
* @param values A map of column names (keys) and new values (values).
* @param where An SQL "WHERE" clause that selects records based on their column values. If this
* is null, then all records that match the URI pattern are selected.
* @param whereArgs An array of selection criteria. If the "where" param contains value
* placeholders ("?"), then each placeholder is replaced by the corresponding element in the
* array.
* @return The number of rows updated.
* @throws IllegalArgumentException if the incoming URI pattern is invalid.
*/
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
// Opens the database object in "write" mode.
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
String finalWhere;
// Does the update based on the incoming URI pattern
switch (sUriMatcher.match(uri)) {
// If the incoming URI matches the general notes pattern, does the update based on
// the incoming data.
case NOTES:
// Does the update and returns the number of rows updated.
count = db.update(
NotePad.Notes.TABLE_NAME, // The database table name.
values, // A map of column names and new values to use.
where, // The where clause column names.
whereArgs // The where clause column values to select on.
);
break;
// If the incoming URI matches a single note ID, does the update based on the incoming
// data, but modifies the where clause to restrict it to the particular note ID.
case NOTE_ID:
// From the incoming URI, get the note ID
String noteId = uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION);
/*
* Starts creating the final WHERE clause by restricting it to the incoming
* note ID.
*/
finalWhere =
NotePad.Notes._ID + // The ID column name
" = " + // test for equality
uri.getPathSegments(). // the incoming note ID
get(NotePad.Notes.NOTE_ID_PATH_POSITION)
;
// If there were additional selection criteria, append them to the final WHERE
// clause
if (where !=null) {
finalWhere = finalWhere + " AND " + where;
}
// Does the update and returns the number of rows updated.
count = db.update(
NotePad.Notes.TABLE_NAME, // The database table name.
values, // A map of column names and new values to use.
finalWhere, // The final WHERE clause to use
// placeholders for whereArgs
whereArgs // The where clause column values to select on, or
// null if the values are in the where argument.
);
break;
// If the incoming pattern is invalid, throws an exception.
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
/*Gets a handle to the content resolver object for the current context, and notifies it
* that the incoming URI changed. The object passes this along to the resolver framework,
* and observers that have registered themselves for the provider are notified.
*/
getContext().getContentResolver().notifyChange(uri, null);
// Returns the number of rows updated.
return count;
}
/**
* A test package can call this to get a handle to the database underlying NotePadProvider,
* so it can insert test data into the database. The test case class is responsible for
* instantiating the provider in a test context; {@link android.test.ProviderTestCase2} does
* this during the call to setUp()
*
* @return a handle to the database helper object for the provider's data.
*/
DatabaseHelper getOpenHelperForTest() {
return mOpenHelper;
}
}
//src\com\example\android\notepad\NotesList.java
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 com.example.android.notepad;
import com.example.android.notepad.NotePad;
import android.app.ListActivity;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
/**
* Displays a list of notes. Will display notes from the {@link Uri}
* provided in the incoming Intent if there is one, otherwise it defaults to displaying the
* contents of the {@link NotePadProvider}.
*
* NOTE: Notice that the provider operations in this Activity are taking place on the UI thread.
* This is not a good practice. It is only done here to make the code more readable. A real
* application should use the {@link android.content.AsyncQueryHandler} or
* {@link android.os.AsyncTask} object to perform operations asynchronously on a separate thread.
*/
public class NotesList extends ListActivity {
// For logging and debugging
private static final String TAG = "NotesList";
/**
* The columns needed by the cursor adapter
*/
private static final String[] PROJECTION = new String[] {
NotePad.Notes._ID, // 0
NotePad.Notes.COLUMN_NAME_TITLE, // 1
};
/** The index of the title column */
private static final int COLUMN_INDEX_TITLE = 1;
/**
* onCreate is called when Android starts this Activity from scratch.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The user does not need to hold down the key to use menu shortcuts.
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
/* If no data is given in the Intent that started this Activity, then this Activity
* was started when the intent filter matched a MAIN action. We should use the default
* provider URI.
*/
// Gets the intent that started this Activity.
Intent intent = getIntent();
// If there is no data associated with the Intent, sets the data to the default URI, which
// accesses a list of notes.
if (intent.getData() == null) {
intent.setData(NotePad.Notes.CONTENT_URI);
}
/*
* Sets the callback for context menu activation for the ListView. The listener is set
* to be this Activity. The effect is that context menus are enabled for items in the
* ListView, and the context menu is handled by a method in NotesList.
*/
getListView().setOnCreateContextMenuListener(this);
/* Performs a managed query. The Activity handles closing and requerying the cursor
* when needed.
*
* Please see the introductory note about performing provider operations on the UI thread.
*/
Cursor cursor = managedQuery(
getIntent().getData(), // Use the default content URI for the provider.
PROJECTION, // Return the note ID and title for each note.
null, // No where clause, return all records.
null, // No where clause, therefore no where column values.
NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order.
);
/*
* The following two arrays create a "map" between columns in the cursor and view IDs
* for items in the ListView. Each element in the dataColumns array represents
* a column name; each element in the viewID array represents the ID of a View.
* The SimpleCursorAdapter maps them in ascending order to determine where each column
* value will appear in the ListView.
*/
// The names of the cursor columns to display in the view, initialized to the title column
String[] dataColumns = { NotePad.Notes.COLUMN_NAME_TITLE } ;
// The view IDs that will display the cursor columns, initialized to the TextView in
// noteslist_item.xml
int[] viewIDs = { android.R.id.text1 };
// Creates the backing adapter for the ListView.
SimpleCursorAdapter adapter
= new SimpleCursorAdapter(
this, // The Context for the ListView
R.layout.noteslist_item, // Points to the XML for a list item
cursor, // The cursor to get items from
dataColumns,
viewIDs
);
// Sets the ListView's adapter to be the cursor adapter that was just created.
setListAdapter(adapter);
}
/**
* Called when the user clicks the device's Menu button the first time for
* this Activity. Android passes in a Menu object that is populated with items.
*
* Sets up a menu that provides the Insert option plus a list of alternative actions for
* this Activity. Other applications that want to handle notes can "register" themselves in
* Android by providing an intent filter that includes the category ALTERNATIVE and the
* mimeTYpe NotePad.Notes.CONTENT_TYPE. If they do this, the code in onCreateOptionsMenu()
* will add the Activity that contains the intent filter to its list of options. In effect,
* the menu will offer the user other applications that can handle notes.
* @param menu A Menu object, to which menu items should be added.
* @return True, always. The menu should be displayed.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate menu from XML resource
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list_options_menu, menu);
// 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
// our menu with their own actions.
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, NotesList.class), null, intent, 0, null);
return super.onCreateOptionsMenu(menu);
}
@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);
MenuItem mPasteItem = menu.findItem(R.id.menu_paste);
// If the clipboard contains an item, enables the Paste option on the menu.
if (clipboard.hasPrimaryClip()) {
mPasteItem.setEnabled(true);
} else {
// If the clipboard is empty, disables the menu's Paste option.
mPasteItem.setEnabled(false);
}
// Gets the number of notes currently being displayed.
final boolean haveItems = getListAdapter().getCount() > 0;
// If there are any notes in the list (which implies that one of
// them is selected), then we need to generate the actions that
// can be performed on the current selection. This will be a combination
// of our own specific actions along with any extensions that can be
// found.
if (haveItems) {
// This is the selected item.
Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());
// Creates an array of Intents with one element. This will be used to send an Intent
// based on the selected menu item.
Intent[] specifics = new Intent[1];
// Sets the Intent in the array to be an EDIT action on the URI of the selected note.
specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
// Creates an array of menu items with one element. This will contain the EDIT option.
MenuItem[] items = new MenuItem[1];
// Creates an Intent with no specific action, using the URI of the selected note.
Intent intent = new Intent(null, uri);
/* Adds the category ALTERNATIVE to the Intent, with the note ID URI as its
* data. This prepares the Intent as a place to group alternative options in the
* menu.
*/
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
/*
* Add alternatives to the menu
*/
menu.addIntentOptions(
Menu.CATEGORY_ALTERNATIVE, // Add the Intents as options in the alternatives group.
Menu.NONE, // A unique item ID is not required.
Menu.NONE, // The alternatives don't need to be in order.
null, // The caller's name is not excluded from the group.
specifics, // These specific options must appear first.
intent, // These Intent objects map to the options in specifics.
Menu.NONE, // No flags are required.
items // The menu items generated from the specifics-to-
// Intents mapping
);
// If the Edit menu item exists, adds shortcuts for it.
if (items[0] != null) {
// Sets the Edit menu item shortcut to numeric "1", letter "e"
items[0].setShortcut('1', 'e');
}
} else {
// If the list is empty, removes any existing alternative actions from the menu
menu.removeGroup(Menu.CATEGORY_ALTERNATIVE);
}
// Displays the menu
return true;
}
/**
* This method is called when the user selects an option from the menu, but no item
* in the list is selected. If the option was INSERT, then a new Intent is sent out with action
* ACTION_INSERT. The data from the incoming Intent is put into the new Intent. In effect,
* this triggers the NoteEditor activity in the NotePad application.
*
* If the item was not INSERT, then most likely it was an alternative option from another
* application. The parent method is called to process the item.
* @param item The menu item that was selected by the user
* @return True, if the INSERT menu item was selected; otherwise, the result of calling
* the parent method.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_add:
/*
* Launches a new Activity using an Intent. The intent filter for the Activity
* has to have action ACTION_INSERT. No category is set, so DEFAULT is assumed.
* In effect, this starts the NoteEditor Activity in NotePad.
*/
startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));
return true;
case R.id.menu_paste:
/*
* Launches a new Activity using an Intent. The intent filter for the Activity
* has to have action ACTION_PASTE. No category is set, so DEFAULT is assumed.
* In effect, this starts the NoteEditor Activity in NotePad.
*/
startActivity(new Intent(Intent.ACTION_PASTE, getIntent().getData()));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* This method is called when the user context-clicks a note in the list. NotesList registers
* itself as the handler for context menus in its ListView (this is done in onCreate()).
*
* The only available options are COPY and DELETE.
*
* Context-click is equivalent to long-press.
*
* @param menu A ContexMenu object to which items should be added.
* @param view The View for which the context menu is being constructed.
* @param menuInfo Data associated with view.
* @throws ClassCastException
*/
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
// The data from the menu item.
AdapterView.AdapterContextMenuInfo info;
// Tries to get the position of the item in the ListView that was long-pressed.
try {
// Casts the incoming data object into the type for AdapterView objects.
info = (AdapterView.AdapterContextMenuInfo) menuInfo;
} catch (ClassCastException e) {
// If the menu object can't be cast, logs an error.
Log.e(TAG, "bad menuInfo", e);
return;
}
/*
* Gets the data associated with the item at the selected position. getItem() returns
* whatever the backing adapter of the ListView has associated with the item. In NotesList,
* the adapter associated all of the data for a note with its list item. As a result,
* getItem() returns that data as a Cursor.
*/
Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
// If the cursor is empty, then for some reason the adapter can't get the data from the
// provider, so returns null to the caller.
if (cursor == null) {
// For some reason the requested item isn't available, do nothing
return;
}
// Inflate menu from XML resource
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list_context_menu, menu);
// Sets the menu header to be the title of the selected note.
menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE));
// Append to the
// menu items for any other activities that can do stuff with it
// as well. This does a query on the system for any activities that
// implement the ALTERNATIVE_ACTION for our data, adding a menu item
// for each one that is found.
Intent intent = new Intent(null, Uri.withAppendedPath(getIntent().getData(),
Integer.toString((int) info.id) ));
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, NotesList.class), null, intent, 0, null);
}
/**
* This method is called when the user selects an item from the context menu
* (see onCreateContextMenu()). The only menu items that are actually handled are DELETE and
* COPY. Anything else is an alternative option, for which default handling should be done.
*
* @param item The selected menu item
* @return True if the menu item was DELETE, and no default processing is need, otherwise false,
* which triggers the default handling of the item.
* @throws ClassCastException
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
// The data from the menu item.
AdapterView.AdapterContextMenuInfo info;
/*
* Gets the extra info from the menu item. When an note in the Notes list is long-pressed, a
* context menu appears. The menu items for the menu automatically get the data
* associated with the note that was long-pressed. The data comes from the provider that
* backs the list.
*
* The note's data is passed to the context menu creation routine in a ContextMenuInfo
* object.
*
* When one of the context menu items is clicked, the same data is passed, along with the
* note ID, to onContextItemSelected() via the item parameter.
*/
try {
// Casts the data object in the item into the type for AdapterView objects.
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
} catch (ClassCastException e) {
// If the object can't be cast, logs an error
Log.e(TAG, "bad menuInfo", e);
// Triggers default processing of the menu item.
return false;
}
// Appends the selected note's ID to the URI sent with the incoming Intent.
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
/*
* Gets the menu item's ID and compares it to known actions.
*/
switch (item.getItemId()) {
case R.id.context_open:
// Launch activity to view/edit the currently selected item
startActivity(new Intent(Intent.ACTION_EDIT, noteUri));
return true;
case R.id.context_copy:
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
// Copies the notes URI to the clipboard. In effect, this copies the note itself
clipboard.setPrimaryClip(ClipData.newUri( // new clipboard item holding a URI
getContentResolver(), // resolver to retrieve URI info
"Note", // label for the clip
noteUri) // the URI
);
// Returns to the caller and skips further processing.
return true;
case R.id.context_delete:
// Deletes the note from the provider by passing in a URI in note ID format.
// Please see the introductory note about performing provider operations on the
// UI thread.
getContentResolver().delete(
noteUri, // The URI of the provider
null, // No where clause is needed, since only a single note ID is being
// passed in.
null // No where clause is used, so no where arguments are needed.
);
// Returns to the caller and skips further processing.
return true;
default:
return super.onContextItemSelected(item);
}
}
/**
* This method is called when the user clicks a note in the displayed list.
*
* This method handles incoming actions of either PICK (get data from the provider) or
* GET_CONTENT (get or create data). If the incoming action is EDIT, this method sends a
* new Intent to start NoteEditor.
* @param l The ListView that contains the clicked item
* @param v The View of the individual item
* @param position The position of v in the displayed list
* @param id The row ID of the clicked item
*/
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// Constructs a new URI from the incoming URI and the row ID
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
// Gets the action from the incoming Intent
String action = getIntent().getAction();
// Handles requests for note data
if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {
// Sets the result to return to the component that called this Activity. The
// result contains the new URI
setResult(RESULT_OK, new Intent().setData(uri));
} else {
// Sends out an Intent to start an Activity that can handle ACTION_EDIT. The
// Intent's data is the note ID URI. The effect is to call NoteEdit.
startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}
}
//src\com\example\android\notepad\NotesLiveFolder.java
/*
* Copyright (C) 2009 The Android Open Source Project
*
* 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 com.example.android.notepad;
import com.example.android.notepad.NotePad;
import android.app.Activity;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.os.Bundle;
import android.provider.LiveFolders;
/**
* This Activity creates a live folder Intent and
* sends it back to HOME. From the data in the Intent, HOME creates a live folder and displays
* its icon in the Home view.
* When the user clicks the icon, Home uses the data it got from the Intent to retrieve information
* from a content provider and display it in a View.
*
* The intent filter for this Activity is set to ACTION_CREATE_LIVE_FOLDER, which
* HOME sends in response to a long press and selection of Live Folder.
*/
public class NotesLiveFolder extends Activity {
/**
* All of the work is done in onCreate(). The Activity doesn't actually display a UI.
* Instead, it sets up an Intent and returns it to its caller (the HOME activity).
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Gets the incoming Intent and its action. If the incoming Intent was
* ACTION_CREATE_LIVE_FOLDER, then create an outgoing Intent with the
* necessary data and send back OK. Otherwise, send back CANCEL.
*/
final Intent intent = getIntent();
final String action = intent.getAction();
if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
// Creates a new Intent.
final Intent liveFolderIntent = new Intent();
/*
* The following statements put data into the outgoing Intent. Please see
* {@link android.provider.LiveFolders for a detailed description of these
* data values. From this data, HOME sets up a live folder.
*/
// Sets the URI pattern for the content provider backing the folder.
liveFolderIntent.setData(NotePad.Notes.LIVE_FOLDER_URI);
// Adds the display name of the live folder as an Extra string.
String foldername = getString(R.string.live_folder_name);
liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, foldername);
// Adds the display icon of the live folder as an Extra resource.
ShortcutIconResource foldericon =
Intent.ShortcutIconResource.fromContext(this, R.drawable.live_folder_notes);
liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON, foldericon);
// Add the display mode of the live folder as an integer. The specified
// mode causes the live folder to display as a list.
liveFolderIntent.putExtra(
LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,
LiveFolders.DISPLAY_MODE_LIST);
/*
* Adds a base action for items in the live folder list, as an Intent. When the
* user clicks an individual note in the list, the live folder fires this Intent.
*
* Its action is ACTION_EDIT, so it triggers the Note Editor activity. Its
* data is the URI pattern for a single note identified by its ID. The live folder
* automatically adds the ID value of the selected item to the URI pattern.
*
* As a result, Note Editor is triggered and gets a single note to retrieve by ID.
*/
Intent returnIntent
= new Intent(Intent.ACTION_EDIT, NotePad.Notes.CONTENT_ID_URI_PATTERN);
liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, returnIntent);
/* Creates an ActivityResult object to propagate back to HOME. Set its result indicator
* to OK, and sets the returned Intent to the live folder Intent that was just
* constructed.
*/
setResult(RESULT_OK, liveFolderIntent);
} else {
// If the original action was not ACTION_CREATE_LIVE_FOLDER, creates an
// ActivityResult with the indicator set to CANCELED, but do not return an Intent
setResult(RESULT_CANCELED);
}
// Closes the Activity. The ActivityObject is propagated back to the caller.
finish();
}
}
//src\com\example\android\notepad\TitleEditor.java
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 com.example.android.notepad;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
/**
* This Activity allows the user to edit a note's title. It displays a floating window
* containing an EditText.
*
* NOTE: Notice that the provider operations in this Activity are taking place on the UI thread.
* This is not a good practice. It is only done here to make the code more readable. A real
* application should use the {@link android.content.AsyncQueryHandler}
* or {@link android.os.AsyncTask} object to perform operations asynchronously on a separate thread.
*/
public class TitleEditor extends Activity {
/**
* This is a special intent action that means "edit the title of a note".
*/
public static final String EDIT_TITLE_ACTION = "com.android.notepad.action.EDIT_TITLE";
// Creates a projection that returns the note ID and the note contents.
private static final String[] PROJECTION = new String[] {
NotePad.Notes._ID, // 0
NotePad.Notes.COLUMN_NAME_TITLE, // 1
};
// The position of the title column in a Cursor returned by the provider.
private static final int COLUMN_INDEX_TITLE = 1;
// A Cursor object that will contain the results of querying the provider for a note.
private Cursor mCursor;
// An EditText object for preserving the edited title.
private EditText mText;
// A URI object for the note whose title is being edited.
private Uri mUri;
/**
* This method is called by Android when the Activity is first started. From the incoming
* Intent, it determines what kind of editing is desired, and then does it.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the View for this Activity object's UI.
setContentView(R.layout.title_editor);
// Get the Intent that activated this Activity, and from it get the URI of the note whose
// title we need to edit.
mUri = getIntent().getData();
/*
* Using the URI passed in with the triggering Intent, gets the note.
*
* Note: This is being done on the UI thread. It will block the thread until the query
* completes. In a sample app, going against a simple provider based on a local database,
* the block will be momentary, but in a real app you should use
* android.content.AsyncQueryHandler or android.os.AsyncTask.
*/
mCursor = managedQuery(
mUri, // The URI for the note that is to be retrieved.
PROJECTION, // The columns to retrieve
null, // No selection criteria are used, so no where columns are needed.
null, // No where columns are used, so no where values are needed.
null // No sort order is needed.
);
// Gets the View ID for the EditText box
mText = (EditText) this.findViewById(R.id.title);
}
/**
* This method is called when the Activity is about to come to the foreground. This happens
* when the Activity comes to the top of the task stack, OR when it is first starting.
*
* Displays the current title for the selected note.
*/
@Override
protected void onResume() {
super.onResume();
// Verifies that the query made in onCreate() actually worked. If it worked, then the
// Cursor object is not null. If it is *empty*, then mCursor.getCount() == 0.
if (mCursor != null) {
// The Cursor was just retrieved, so its index is set to one record *before* the first
// record retrieved. This moves it to the first record.
mCursor.moveToFirst();
// Displays the current title text in the EditText object.
mText.setText(mCursor.getString(COLUMN_INDEX_TITLE));
}
}
/**
* This method is called when the Activity loses focus.
*
* For Activity objects that edit information, onPause() may be the one place where changes are
* saved. The Android application model is predicated on the idea that "save" and "exit" aren't
* required actions. When users navigate away from an Activity, they shouldn't have to go back
* to it to complete their work. The act of going away should save everything and leave the
* Activity in a state where Android can destroy it if necessary.
*
* Updates the note with the text currently in the text box.
*/
@Override
protected void onPause() {
super.onPause();
// Verifies that the query made in onCreate() actually worked. If it worked, then the
// Cursor object is not null. If it is *empty*, then mCursor.getCount() == 0.
if (mCursor != null) {
// Creates a values map for updating the provider.
ContentValues values = new ContentValues();
// In the values map, sets the title to the current contents of the edit box.
values.put(NotePad.Notes.COLUMN_NAME_TITLE, mText.getText().toString());
/*
* Updates the provider with the note's new title.
*
* Note: This is being done on the UI thread. It will block the thread until the
* update completes. In a sample app, going against a simple provider based on a
* local database, the block will be momentary, but in a real app you should use
* android.content.AsyncQueryHandler or android.os.AsyncTask.
*/
getContentResolver().update(
mUri, // The URI for the note to update.
values, // The values map containing the columns to update and the values to use.
null, // No selection criteria is used, so no "where" columns are needed.
null // No "where" columns are used, so no "where" values are needed.
);
}
}
public void onClickOk(View v) {
finish();
}
}
//
//res\layout\note_editor.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
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.
-->
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.example.android.notepad.NoteEditor$LinedEditText"
android:id="@+id/note"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:padding="5dp"
android:scrollbars="vertical"
android:fadingEdge="vertical"
android:gravity="top"
android:textSize="22sp"
android:capitalize="sentences"
/>
//res\layout\noteslist_item.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="5dip"
android:singleLine="true"
/>
//res\layout\title_editor.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:paddingBottom="3dip">
<EditText android:id="@+id/title"
android:maxLines="1"
android:layout_marginTop="2dp"
android:layout_marginBottom="15dp"
android:layout_width="wrap_content"
android:ems="25"
android:layout_height="wrap_content"
android:autoText="true"
android:capitalize="sentences"
android:scrollHorizontally="true" />
<Button android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/button_ok"
android:onClick="onClickOk" />
</LinearLayout>
//
//res\menu\editor_options_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_save"
android:icon="@drawable/ic_menu_save"
android:alphabeticShortcut='s'
android:title="@string/menu_save"
android:showAsAction="ifRoom|withText" />
<item android:id="@+id/menu_revert"
android:icon="@drawable/ic_menu_revert"
android:title="@string/menu_revert" />
<item android:id="@+id/menu_delete"
android:icon="@drawable/ic_menu_delete"
android:title="@string/menu_delete"
android:showAsAction="ifRoom|withText" />
</menu>
//res\menu\list_context_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/context_open"
android:title="@string/menu_open" />
<item android:id="@+id/context_copy"
android:title="@string/menu_copy" />
<item android:id="@+id/context_delete"
android:title="@string/menu_delete" />
</menu>
//res\menu\list_options_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- This is our one standard application action (creating a new note). -->
<item android:id="@+id/menu_add"
android:icon="@drawable/ic_menu_compose"
android:title="@string/menu_add"
android:alphabeticShortcut='a'
android:showAsAction="always" />
<!-- If there is currently data in the clipboard, this adds a PASTE menu item to the menu
so that the user can paste in the data.. -->
<item android:id="@+id/menu_paste"
android:icon="@drawable/ic_menu_compose"
android:title="@string/menu_paste"
android:alphabeticShortcut='p' />
</menu>
//
//res\values\strings.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
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.
-->
<resources>
<string name="app_name">NotePad</string>
<string name="live_folder_name">Notes</string>
<string name="title_edit_title">Note title:</string>
<string name="title_create">New note</string>
<string name="title_edit">Edit: %1$s</string>
<string name="title_notes_list">Notes</string>
<string name="menu_add">New note</string>
<string name="menu_save">Save</string>
<string name="menu_delete">Delete</string>
<string name="menu_open">Open</string>
<string name="menu_revert">Revert changes</string>
<string name="menu_copy">Copy</string>
<string name="menu_paste">Paste</string>
<string name="button_ok">OK</string>
<string name="text_title">Title:</string>
<string name="resolve_edit">Edit note</string>
<string name="resolve_title">Edit title</string>
<string name="error_title">Error</string>
<string name="error_message">Error loading note</string>
<string name="nothing_to_save">There is nothing to save</string>
</resources>
Related examples in the same category