Revised Note Pad sample, new test app for Note Pad

Change-Id: Ia41a33d935ead704c1de439a0cfb0a55806cfe12
diff --git a/samples/NotePad/AndroidManifest.xml b/samples/NotePad/AndroidManifest.xml
index 7d41dd5..560f780 100644
--- a/samples/NotePad/AndroidManifest.xml
+++ b/samples/NotePad/AndroidManifest.xml
@@ -1,39 +1,82 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
+<!-- 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
+    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
+        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.
+    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.
 -->
 
-<!-- Declare the contents of this Android application.  The namespace
-     attribute brings in the Android platform namespace, and the package
-     supplies a unique name for the application.  When writing your
-     own application, the package name must be changed from "com.example.*"
-     to come from a domain that you own or have control over. -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.android.notepad"
->
-    <application android:icon="@drawable/app_notes"
-        android:label="@string/app_name"
-    >
-        <provider android:name="NotePadProvider"
-            android:authorities="com.google.provider.NotePad"
-        />
+<!--
+     The manifest declares information about this Android application that is of
+     interest to the system and to other applications.
+     The namespace  attribute brings in the Android platform namespace. The package name is
+     an Android identifier for the application. It uses names in the Java package id format,
+     but is not dependent on or linked to any of the Java package names in the application.
+     It must be unique. If you use this manifest as part of your own application, you must
+     change the name prefix from "com.example." to a domain that you own or control.
 
-        <activity android:name="NotesList" android:label="@string/title_notes_list">
+-->
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.example.android.notepad"
+        android:versionCode="1"
+        android:versionName="1.0">
+
+    <!--
+        Declares the attributes of the application.
+        The icon and label are displayed in the Launcher and in
+        other utilities that list applications.
+    -->
+    <application
+        android:icon="@drawable/app_notes"
+        android:label="@string/app_name" >
+
+    <!--
+        Declares the content provider offered by this application. The name is an
+        identifier. The authorities attribute is stored by the Android system in a global
+        table, along with a handle to the current instance of the provider.
+        Clients can then link to the provider through the global map by providing
+        the authority string to Android.
+    -->
+
+        <provider
+            android:name="NotePadProvider"
+            android:authorities="com.example.android.notepad.NotePad" />
+
+        <!--
+            Declares the NotesList activity, and gives it a name and a label.
+            The intent filters determine the types of intents that this activity will see.
+            The NotesList activity displays notes and returns single notes instances to
+            other activities.
+          -->
+        <activity
+            android:name="NotesList"
+            android:label="@string/title_notes_list">
+            <!--
+                Defines the main entry point into NotePad (MAIN), which doesn't
+                require any additional information. LAUNCHER tells launchers to display this
+                Activity.
+             -->
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+            <!--
+                Defines actions the Activity can do on a directory of notes.
+                It allows the user to view a list of notes, edit a list of notes, or pick one
+                of the notes. The intent's MIME type must match the MIME type specified by the
+                <data> element. That element specifies a cursor of data containing
+                vnd.google.note objects (defined in this app).
+                Notice that all intent filters that don't specify MAIN must specify
+                DEFAULT as a category
+             -->
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <action android:name="android.intent.action.EDIT" />
@@ -41,6 +84,11 @@
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
             </intent-filter>
+            <!--
+                Defines an action the Activity can do for a single note. If the action is
+                GET_CONTENT, then the Activity can return a cursor containing at most 1
+                note object (an item) identified as a vnd.google.note item.
+             -->
             <intent-filter>
                 <action android:name="android.intent.action.GET_CONTENT" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -48,14 +96,26 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="NoteEditor"
+        <!--
+             Defines the NoteEditor activity. This activity edits or displays a single note.
+             The screen orientation displayed by this activity depends on the device orientation
+             (screenOrientation).
+             The activity uses a light theme (android:theme)
+             When the keyboard is closed (or hidden) or the device orientation changes, the
+             Activity will handle it. The Activity does not restart, but onConfigurationChanged() is
+             called (This has been deprecated; developers should let Android call onDestroy() and
+             then restore the app state in onCreate()
+         -->
+        <activity
+            android:name="NoteEditor"
             android:theme="@android:style/Theme.Light"
             android:label="@string/title_note"
             android:screenOrientation="sensor"
-            android:configChanges="keyboardHidden|orientation"
-        >
-            <!-- This filter says that we can view or edit the data of
-                 a single note -->
+            android:configChanges="keyboardHidden|orientation" >
+            <!--
+                Allows viewing or editing data, including the application-created EDIT_NOTE
+                intent, for a single note of type vnd.google.note
+            -->
             <intent-filter android:label="@string/resolve_edit">
                 <action android:name="android.intent.action.VIEW" />
                 <action android:name="android.intent.action.EDIT" />
@@ -64,10 +124,9 @@
                 <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
             </intent-filter>
 
-            <!-- This filter says that we can create a new note inside
-                 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. -->
+            <!--
+                Allows inserting a note into a list of notes of type vnd.google.note
+             -->
             <intent-filter>
                 <action android:name="android.intent.action.INSERT" />
                 <action android:name="android.intent.action.PASTE" />
@@ -77,42 +136,53 @@
 
         </activity>
 
-        <activity android:name="TitleEditor"
+        <!--
+            Defines the title editor Activity. This Activity uses a dialog view, which
+            looks like a regular dialog but is reachable by an Intent and uses
+            intent filters. The soft keyboard is made visible when input is expected.
+         -->
+        <activity
+            android:name="TitleEditor"
             android:label="@string/title_edit_title"
             android:theme="@android:style/Theme.Dialog"
             android:windowSoftInputMode="stateVisible">
-            <!-- This activity implements an alternative action that can be
-                 performed on notes: editing their title.  It can be used as
-                 a default operation if the user invokes this action, and is
-                 available as an alternative action for any note data. -->
+            <!--
+                This intent-filter accepts the application-specific EDIT_TITLE intent. It
+                also accepts DEFAULT (required by non-MAIN activities). Because it is specified
+                with ALTERNATIVE and SELECTED_ALTERNATIVE, the activity will appear as an
+                alternative in a list of activities that work on the data type listed.
+                This activity can only work on a single notes object.
+             -->
             <intent-filter android:label="@string/resolve_title">
-                <!-- This is the action we perform.  It is a custom action we
-                     define for our application, not a generic VIEW or EDIT
-                     action since we are not a general note viewer/editor. -->
                 <action android:name="com.android.notepad.action.EDIT_TITLE" />
-                <!-- DEFAULT: execute if being directly invoked. -->
                 <category android:name="android.intent.category.DEFAULT" />
-                <!-- ALTERNATIVE: show as an alternative action when the user is
-                     working with this type of data. -->
                 <category android:name="android.intent.category.ALTERNATIVE" />
-                <!-- SELECTED_ALTERNATIVE: show as an alternative action the user
-                     can perform when selecting this type of data. -->
                 <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
-                <!-- This is the data type we operate on. -->
                 <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
             </intent-filter>
         </activity>
-
-        <activity android:name="NotesLiveFolder" android:label="@string/live_folder_name"
+        <!--
+            This activity handles live folder views of notes. The CREATE_LIVE_FOLDER filter
+            tells the Launcher that this activity can handle live folders.
+        -->
+        <activity
+            android:name="NotesLiveFolder"
+            android:label="@string/live_folder_name"
             android:icon="@drawable/live_folder_notes">
             <intent-filter>
                 <action android:name="android.intent.action.CREATE_LIVE_FOLDER" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-
+        <!--
+            This declares version information for the application:
+            targetSdkVersion: Indicates that the application was tested with API level 4.
+            Android will turn off compatibility features for APIs greater than 4, since the
+            application does not require them. Also, if the application were to be uploaded to
+            Android Market, then it would not be visible to devices running an API level below 4.
+            minSdkVersion: Indicates that Android will not install this application to a
+            device that is running an API level less than 4.
+        -->
+        <uses-sdk android:targetSdkVersion="3" android:minSdkVersion="3"/>
     </application>
-
-    <uses-sdk android:targetSdkVersion="4" android:minSdkVersion="3"/>
 </manifest>
-
diff --git a/samples/NotePad/_index.html b/samples/NotePad/_index.html
index 8f680ef..d263fda 100644
--- a/samples/NotePad/_index.html
+++ b/samples/NotePad/_index.html
@@ -1,20 +1,62 @@
-<p>A simple note pad application.
-It demonstrates:</p>
-<ul> 
-<li>Using views</li>
-<li>Accessing a database</li>
-<li>Using an intent to open a new window</li>
-<li>Managing activity lifecycle</li>
-<li>Creating <a href="../../../reference/android/provider/LiveFolders.html">Live Folders</a></li>
-<li>And more...</li>
+<h2>NotePad Documentation</h2>
+<p>
+    NotePad is a simple application for entering and saving character-only notes.
+    It demonstrates the following Android concepts:
+</p>
+<ul>
+    <li>ListView objects with Cursor-based adapters.</li>
+    <li>Data storage and access with a ContentProvider.</li>
+    <li>Intent filters that handle multiple actions.</li>
+    <li>Activity objects that handle more than one Intent.</li>
+    <li>Sending an Intent by selecting an item from a ListView.</li>
+    <li>Sending data in an Intent.</li>
+    <li>Dynamic construction of menus.</li>
+    <li>Context menus.</li>
+    <li>Providing other applications as alternative actions for a data item.</li>
+    <li>Creating <a href="../../../reference/android/provider/LiveFolders.html">
+    Live Folders</a></li>
+    <li>Setting up an application that does not use "save" or "exit". The application
+    saves the user's work whenever focus moves to another task. It remains available until
+    the system destroys it.</li>
 </ul>
-
-<p class="note">Please notice that this is not the same
-notepad code that's used for the <a href="../../../guide/tutorials/notepad/index.html">Notepad Tutorial</a>. 
-They are similar in nature, but there are several differences in implementation &mdash; the tutorial
-is slightly more simple. If you're new to 
-Android development, we suggest you start with the tutorial, then visit this
-code later to see more sample code.</p>
-
+<h4>Functions</h4>
+<p>
+    By default, NotePad displays a list of existing notes,
+    and provides a menu that allows you to insert a new note. If you click an existing note, you
+    can edit it, or select the menu to delete it or edit its title. If you context-click an
+    existing note, you can also delete it. If you navigate away from a new note once you've entered
+    text, NotePad automatically saves what you've entered.
+</p>
+<h4>Limitations</h4>
+<p>
+    NotePad has limited functionality. It does not provide search/replace. It does not support
+    HTML or any other type of markup. However, it does have copy/paste if it is running on a
+    version of the Android platform that supports that functionality.
+</p>
+<h4>Provider</h4>
+<p>
+    Other applications can use NotePad's content provider to store notes. The "contract" for
+    the NotePad provider is documented in the Appendix.
+</p>
+<h4>Alternative actions</h4>
+<p>
+    Other applications can also register themselves to handle NotePad notes. To do this, they must
+    provide the necessary specifications in their manifest file. The specifications are listed in
+    the Appendix.
+</p>
+<h4>Test package</h4>
+<p>
+    This sample also contains a test package that demonstrates how to test a ContentProvider and
+    an Activity. To learn more about this test package, please read
+    <a href="tests/index.html">NotePadTest Documentation</a>
+</p>
+<p class="note">
+    Please notice that this is not the same code that's used for the
+    <a href="../../../guide/tutorials/notepad/index.html">Notepad Tutorial</a>. This code has been
+    updated more recently, and the tutorial is a more simple implementation. If you're new to
+    Android development, you should start with the tutorial, then return to this code.
+</p>
+<h4>Appendix A: Provider contract</h4>
+<h4>Appendix B: Registering an application</h4>
 <img alt="" src="../images/sample_notepad.png" />
 <img alt="" src="../images/sample_note.png" />
diff --git a/samples/NotePad/res/layout/note_editor.xml b/samples/NotePad/res/layout/note_editor.xml
index d7da99e..dae6d7f 100644
--- a/samples/NotePad/res/layout/note_editor.xml
+++ b/samples/NotePad/res/layout/note_editor.xml
@@ -4,9 +4,9 @@
      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.
@@ -14,11 +14,15 @@
      limitations under the License.
 -->
 
+<!--
+    android:layout_width and android:layout_height use "fill_parent" to be compatible with API
+    level 4. The setting was renamed "match_parent" in API level 8.
+ -->
 <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:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:background="@android:color/transparent"
     android:padding="5dip"
     android:scrollbars="vertical"
diff --git a/samples/NotePad/res/layout/noteslist_item.xml b/samples/NotePad/res/layout/noteslist_item.xml
index e11c5ee..ddb468d 100644
--- a/samples/NotePad/res/layout/noteslist_item.xml
+++ b/samples/NotePad/res/layout/noteslist_item.xml
@@ -4,19 +4,22 @@
      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.
 -->
-
+<!--
+    android:layout_width and android:layout_height use "fill_parent" to be compatible with API
+    level 4. The setting was renamed "match_parent" in API level 8.
+ -->
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@android:id/text1"
-    android:layout_width="match_parent"
+    android:layout_width="fill_parent"
     android:layout_height="?android:attr/listPreferredItemHeight"
     android:textAppearance="?android:attr/textAppearanceLarge"
     android:gravity="center_vertical"
diff --git a/samples/NotePad/src/com/example/android/notepad/NoteEditor.java b/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
index 57b4646..d01974e 100644
--- a/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
+++ b/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
@@ -16,13 +16,10 @@
 
 package com.example.android.notepad;
 
-import com.example.android.notepad.NotePad.Notes;
+import com.example.android.notepad.NotePad;
 
 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;
@@ -39,28 +36,31 @@
 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 empty note
- * {@link Intent#ACTION_INSERT}, or create a new note from the current contents
- * of the clipboard {@link Intent#ACTION_PASTE}.
+ * 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}, or create a note {@link Intent#ACTION_INSERT}.
+ *
+ * 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 = "Notes";
 
-    /**
-     * Standard projection for the interesting columns of a normal note.
+    /*
+     * Creates a projection that returns the note ID and the note contents.
      */
-    private static final String[] PROJECTION = new String[] {
-            Notes._ID, // 0
-            Notes.NOTE, // 1
-            Notes.TITLE, // 2
+    private static final String[] PROJECTION
+        = new String[] {
+            NotePad.Notes._ID,
+            NotePad.Notes.COLUMN_NAME_NOTE
     };
+
     /** 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";
 
@@ -72,73 +72,111 @@
     // 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;
 
+    // Global variables
     private int mState;
-    private boolean mNoteOnly = false;
     private Uri mUri;
     private Cursor mCursor;
     private EditText mText;
     private String mOriginalContent;
 
     /**
-     * A custom EditText that draws lines between each line of text that is displayed.
+     * 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;
 
-        // we need this constructor for LayoutInflater
+        // 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();
 
-        // Do some setup based on the action being performed.
+        /*
+         *  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)) {
-            // Requested to edit: set that state, and the data being edited.
+
+            // Sets the Activity state to EDIT, and gets the URI for the data to be edited.
             mState = STATE_EDIT;
             mUri = intent.getData();
-        } 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.
+
+        // For an insert action:
+        } else if (Intent.ACTION_INSERT.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 we were unable to create a new note, then just finish
-            // this activity.  A RESULT_CANCELED will be sent back to the
-            // original activity if they requested a result.
+            /*
+             * 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;
             }
@@ -147,266 +185,425 @@
             // 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;
-            }
-
+        // If the action was other than EDIT or INSERT:
         } else {
-            // Whoops, unknown action!  Bail.
+            // 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;
         }
 
-        // Set the layout for this activity.  You can find it in res/layout/note_editor.xml
+        // Sets the layout for this Activity. See res/layout/note_editor.xml
         setContentView(R.layout.note_editor);
-        
-        // The text view for our note, identified by its ID in the XML file.
+
+        // Gets a handle to the EditText in the the layout.
         mText = (EditText) findViewById(R.id.note);
 
-        // Get the note!
-        mCursor = managedQuery(mUri, PROJECTION, null, null, null);
+        /*
+         * 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)
+        );
 
-        // If an instance of this activity had previously stopped, we can
-        // get the original text it started with.
+        /*
+         * 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();
 
-        // If we didn't have any trouble retrieving the data, it is now
-        // time to get at the stuff.
+        /*
+         * 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) {
-            // Make sure we are at the one and only row in the cursor.
+
+            /* 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();
 
-            // Modify our overall title depending on the mode we are running in.
+            // Modifies the window title for the Activity according to the current Activity state.
             if (mState == STATE_EDIT) {
+
+                // Sets the title to "edit"
                 setTitle(getText(R.string.title_edit));
-            } else if (mState == STATE_INSERT || mState == STATE_PASTE) {
+            } else if (mState == STATE_INSERT) {
+
+                // Sets the title to "create"
                 setTitle(getText(R.string.title_create));
             }
 
-            // This is a little tricky: we may be resumed after previously being
-            // paused/stopped.  We want to put the new text in the text view,
-            // but leave the user where they were (retain the cursor position
-            // etc).  This version of setText does that for us.
+            /*
+             * 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.
             String note = mCursor.getString(COLUMN_INDEX_NOTE);
             mText.setTextKeepState(note);
-            
-            // If we hadn't previously retrieved the original text, do so
-            // now.  This allows the user to revert their changes.
+
+            // 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
+        // Saves 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();
 
-        // The user is going somewhere else, so make sure their current
-        // changes are safely saved away in the provider.  We don't need
-        // to do this if only editing.
+        /*
+         * 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) {
-            String text = mText.getText().toString();
-            int length = text.length();
 
-            // If this activity is finished, and there is no text, then we
-            // do something a little special: simply delete the note entry.
-            // Note that we do this both for editing and inserting...  it
-            // would be reasonable to only do it when inserting.
-            if (isFinishing() && (length == 0) && !mNoteOnly) {
+            // Get the current note text.
+            String text = mText.getText().toString();
+            int note_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() && (note_length == 0)) {
                 setResult(RESULT_CANCELED);
                 deleteNote();
 
-            // Get out updates into the provider.
+            /*
+             * 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 {
-                updateNote(text, null, !mNoteOnly);
-            }
-        }
-    }
+                // Creates a map to contain the new values for the columns
+                ContentValues values = new ContentValues();
 
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        super.onCreateOptionsMenu(menu);
+                // In the values map, sets the modification date column to the current time.
+                values.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, System.currentTimeMillis());
 
-        // Build the menus that are shown when editing.
-        if (mState == STATE_EDIT) {
-            menu.add(0, REVERT_ID, 0, R.string.menu_revert)
-                    .setShortcut('0', 'r')
-                    .setIcon(android.R.drawable.ic_menu_revert);
-            if (!mNoteOnly) {
-                menu.add(0, DELETE_ID, 0, R.string.menu_delete)
-                        .setShortcut('1', 'd')
-                        .setIcon(android.R.drawable.ic_menu_delete);
-            }
+                // Creates a title for a newly-inserted note.
+                if (mState == STATE_INSERT) {
 
-        // Build the menus that are shown when inserting.
-        } else {
-            menu.add(0, DISCARD_ID, 0, R.string.menu_discard)
-                    .setShortcut('0', 'd')
-                    .setIcon(android.R.drawable.ic_menu_delete);
-        }
+                    // Gets the first 30 characters of the note, or the entire note if it's
+                    // less than 30 characters long.
+                    String title = text.substring(0, Math.min(30, note_length));
 
-        // If we are working on a full note, then 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.
-        if (!mNoteOnly) {
-            Intent intent = new Intent(null, getIntent().getData());
-            intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
-            menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
-                    new ComponentName(this, NoteEditor.class), null, intent, 0, null);
-        }
-
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        // Handle all of the possible menu actions.
-        switch (item.getItemId()) {
-        case DELETE_ID:
-            deleteNote();
-            finish();
-            break;
-        case DISCARD_ID:
-            cancelNote();
-            break;
-        case REVERT_ID:
-            cancelNote();
-            break;
-        }
-        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) {
+                    // If the note's entire length is greater than 30, then the title is 30
+                    // characters long. Finds the last occurrence of blank in the title, and
+                    // removes all characters to the right of it from the title string.
+                    if (note_length > 30) {
                         int lastSpace = title.lastIndexOf(' ');
                         if (lastSpace > 0) {
                             title = title.substring(0, lastSpace);
                         }
                     }
+                    // In the values map, set the title column to the new title.
+                    values.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
                 }
-                values.put(Notes.TITLE, title);
+
+                // In the values map, sets the note text column to the text in the View.
+                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.
+                );
             }
         }
-
-        // 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
+     * 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) {
+        super.onCreateOptionsMenu(menu);
+
+        // Builds the menus that are shown when editing. These are 'revert' to undo changes, and
+        // 'delete' to delete the note.
+        if (mState == STATE_EDIT) {
+
+            // Adds the 'revert' menu item, and sets its shortcut to numeric 0, letter 'r' and its
+            // icon to the Android standard revert icon.
+            menu.add(0, REVERT_ID, 0, R.string.menu_revert)
+                .setShortcut('0', 'r')
+                .setIcon(android.R.drawable.ic_menu_revert);
+
+            // Adds the 'delete' menu item, and sets its shortcut to numeric 1, letter 'd' and its
+            // icon to the Android standard delete icon
+            menu.add(0, DELETE_ID, 0, R.string.menu_delete)
+                .setShortcut('1', 'd')
+                .setIcon(android.R.drawable.ic_menu_delete);
+
+        // Builds the menus that are shown when inserting. The only option is 'Discard' to throw
+        // away the new note.
+        } else {
+
+            // Adds the 'discard' menu item, using the 'delete' shortcuts and icon.
+            menu.add(0, DISCARD_ID, 0, R.string.menu_discard)
+                    .setShortcut('0', 'd')
+                    .setIcon(android.R.drawable.ic_menu_delete);
+        }
+
+        /*
+         * Appends menu items for any Activity declarations that implement an alternative action
+         * for this Activity's MIME type, one menu item for each Activity.
+         */
+        // Makes a new Intent with the URI data passed to this Activity
+        Intent intent = new Intent(null, getIntent().getData());
+
+        // Adds the ALTERNATIVE category to the Intent.
+        intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+        /*
+         * Constructs a new ComponentName object that represents the current Activity.
+         */
+        ComponentName component = new ComponentName(
+            this,
+            NoteEditor.class);
+
+        /*
+         * In the ALTERNATIVE menu group, adds an option for each Activity that is registered to
+         * handle this Activity's MIME type. The Intent describes what type of items should be
+         * added to the menu; in this case, Activity declarations with category ALTERNATIVE.
+         */
+        menu.addIntentOptions(
+            Menu.CATEGORY_ALTERNATIVE,  // The menu group to add the items to.
+            Menu.NONE,                  // No unique ID is needed.
+            Menu.NONE,                  // No ordering is needed.
+            component,                  // The current Activity object's component name
+            null,                       // No specific items need to be placed first.
+            intent,                     // The intent containing the type of items to add.
+            Menu.NONE,                  // No flags are necessary.
+            null                        // No need to generate an array of menu items.
+        );
+
+        // The method returns TRUE, so that further menu processing is not done.
+        return true;
+    }
+
+    /**
+     * 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) {
+        // Chooses the action to perform
+        switch (item.getItemId()) {
+
+        // Deletes the note and close the Activity.
+        case DELETE_ID:
+            deleteNote();
+            finish();
+            break;
+
+        // Discards the new note.
+        case DISCARD_ID:
+            cancelNote();
+            break;
+
+        // Discards any changes to an edited note.
+        case REVERT_ID:
+            cancelNote();
+            break;
+        }
+
+        // Continues with processing the menu item. In effect, if the item was an alternative
+        // action, this invokes the Activity for that action.
+        return super.onOptionsItemSelected(item);
+    }
+
+    /**
+     * Takes care of canceling work on a note.  Deletes the note if we
      * had created it, otherwise reverts to the original text.
      */
     private final void cancelNote() {
+
+        /*
+         * Tests to see that the original 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) {
+
+            /*
+             * If the user is editing a note, and asked to discard or revert, this puts the
+             * previous note contents back into the note.
+             */
             if (mState == STATE_EDIT) {
-                // Put the original note text back into the database
+
+                // Closes the previous cursor prior to updating the provider
                 mCursor.close();
                 mCursor = null;
+
+                // Creates a new values map
                 ContentValues values = new ContentValues();
-                values.put(Notes.NOTE, mOriginalContent);
-                getContentResolver().update(mUri, values, null, null);
+
+                // Puts the original notes content into the values map. The variable was set in
+                // onResume().
+                values.put(NotePad.Notes.COLUMN_NAME_NOTE, mOriginalContent);
+
+                /*
+                 * Update the provider with the reverted note content.
+                 *
+                 * 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 of the note or notes.
+                    values,  // The reverted values to put into the provider.
+                    null,    // No selection criteria, so no where columns are needed.
+                    null     // No where columns are used, so no where values are needed.
+                );
+
+            /*
+             * If the user was inserting a note and decides to discard it, this deletes the note.
+             */
             } else if (mState == STATE_INSERT) {
-                // We inserted an empty note, make sure to delete it
+                // Deletes the note.
                 deleteNote();
             }
         }
+
+        // Returns a result of CANCELED to the calling Activity.
         setResult(RESULT_CANCELED);
+
+        // Finishes the Activity. Once the user deletes or discards, nothing more can be done, so
+        // return to the calling Activity, either NotesList or some other Activity.
         finish();
     }
 
     /**
-     * Take care of deleting a note.  Simply deletes the entry.
+     * This method deletes a note from the provider.
      */
     private final void deleteNote() {
+        /*
+         * Tests to see that the original 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) {
+
+            // Gets rid of all the Cursor's resources, and deactivates it.
             mCursor.close();
             mCursor = null;
-            getContentResolver().delete(mUri, null, null);
+
+            /*
+             * Deletes the note based on the ID in the URI.
+             *
+             * Note: This is being done on the UI thread. It will block the thread until the
+             * delete 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 android.os.AsyncTask.
+             */
+
+            getContentResolver().delete(
+                mUri,  // The URI of the note to delete.
+                null,  // No selection criteria are specified, so no where columns are needed.
+                null   // No where columns are specified, so no where values are needed.
+            );
+
+            // Throws away any text currently showing in the View.
             mText.setText("");
         }
     }
diff --git a/samples/NotePad/src/com/example/android/notepad/NotePad.java b/samples/NotePad/src/com/example/android/notepad/NotePad.java
index 25be23e..624be90 100644
--- a/samples/NotePad/src/com/example/android/notepad/NotePad.java
+++ b/samples/NotePad/src/com/example/android/notepad/NotePad.java
@@ -20,25 +20,92 @@
 import android.provider.BaseColumns;
 
 /**
- * Convenience definitions for NotePadProvider
+ * 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() {}
-    
+    private NotePad() {
+    }
+
     /**
-     * Notes table
+     * 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("content://" + AUTHORITY + "/notes");
+        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.
@@ -46,7 +113,8 @@
         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.
+         * 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";
 
@@ -55,28 +123,32 @@
          */
         public static final String DEFAULT_SORT_ORDER = "modified DESC";
 
+        /*
+         * Column definitions
+         */
+
         /**
-         * The title of the note
+         * Column name for the title of the note
          * <P>Type: TEXT</P>
          */
-        public static final String TITLE = "title";
+        public static final String COLUMN_NAME_TITLE = "title";
 
         /**
-         * The note itself
+         * Column name of the note content
          * <P>Type: TEXT</P>
          */
-        public static final String NOTE = "note";
+        public static final String COLUMN_NAME_NOTE = "note";
 
         /**
-         * The timestamp for when the note was created
+         * Column name for the creation timestamp
          * <P>Type: INTEGER (long from System.curentTimeMillis())</P>
          */
-        public static final String CREATED_DATE = "created";
+        public static final String COLUMN_NAME_CREATE_DATE = "created";
 
         /**
-         * The timestamp for when the note was last modified
+         * Column name for the modification timestamp
          * <P>Type: INTEGER (long from System.curentTimeMillis())</P>
          */
-        public static final String MODIFIED_DATE = "modified";
+        public static final String COLUMN_NAME_MODIFICATION_DATE = "modified";
     }
 }
diff --git a/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java b/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
index d79be24..c091d87 100644
--- a/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
+++ b/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
@@ -16,15 +16,11 @@
 
 package com.example.android.notepad;
 
-import com.example.android.notepad.NotePad.Notes;
-
 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;
@@ -32,340 +28,582 @@
 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.
+ * This is a content provider for a table of notes. Each note has a title, the note
+ * itself, a creation date and a modified data. The underlying data source is an SQLite
+ * database.
+ *
+ * Notes:
+ * SQLite database method signatures usually include a "where" argument and "whereArgs" argument.
+ * "where" can either specify a full "where" clause in the format used by SQL "WHERE", or a
+ * "where" clause in which the column names are followed by " = ?", or a combination of the two.
+ * If the " = ?" form is present, then whereArgs must be non-null. It is a String array that
+ * contains one element for each "?" present. In order, the "?" symbols are replace by elements
+ * in whereArgs. This feature helps create selection criteria without needing to repeatedly
+ * create a where clause from concatenations. In the comments, "where" is always annotated as
+ * the where clause columns, and "whereArgs" as the where clause values, although "where" can
+ * contain values and "whereArgs" can be null.
+ *
  */
-public class NotePadProvider extends ContentProvider implements PipeDataWriter<Cursor> {
+public class NotePadProvider extends ContentProvider {
 
+    // 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";
-    private static final int DATABASE_VERSION = 2;
-    private static final String NOTES_TABLE_NAME = "notes";
-
-    private static HashMap<String, String> sNotesProjectionMap;
-    private static HashMap<String, String> sLiveFolderProjectionMap;
-
-    private static final int NOTES = 1;
-    private static final int NOTE_ID = 2;
-    private static final int LIVE_FOLDER_NOTES = 3;
-
-    private static final UriMatcher sUriMatcher;
 
     /**
-     * This class helps open, create, and upgrade the database file.
+     * The database version
      */
-    private static class DatabaseHelper extends SQLiteOpenHelper {
+    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;
+
+    /*
+     * 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 " + NOTES_TABLE_NAME + " ("
-                    + Notes._ID + " INTEGER PRIMARY KEY,"
-                    + Notes.TITLE + " TEXT,"
-                    + Notes.NOTE + " TEXT,"
-                    + Notes.CREATED_DATE + " INTEGER,"
-                    + Notes.MODIFIED_DATE + " INTEGER"
+            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);
         }
     }
 
-    private DatabaseHelper mOpenHelper;
-
+    /**
+     *
+     * 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(NOTES_TABLE_NAME);
+        qb.setTables(NotePad.Notes.TABLE_NAME);
 
+        /**
+         * Choose the projection and adjust the "where" clause based on URI pattern-matching.
+         */
         switch (sUriMatcher.match(uri)) {
-        case NOTES:
-            qb.setProjectionMap(sNotesProjectionMap);
-            break;
+            // If the incoming URI is for notes, chooses the Notes projection
+            case NOTES:
+                qb.setProjectionMap(sNotesProjectionMap);
+                break;
 
-        case NOTE_ID:
-            qb.setProjectionMap(sNotesProjectionMap);
-            qb.appendWhere(Notes._ID + "=" + uri.getPathSegments().get(1));
-            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:
-            qb.setProjectionMap(sLiveFolderProjectionMap);
-            break;
+            case LIVE_FOLDER_NOTES:
+                // If the incoming URI is from a live folder, chooses the live folder projection.
+                qb.setProjectionMap(sLiveFolderProjectionMap);
+                break;
 
-        default:
-            throw new IllegalArgumentException("Unknown URI " + uri);
+            default:
+                // If the URI doesn't match any of the known patterns, throw an exception.
+                throw new IllegalArgumentException("Unknown URI " + uri);
         }
 
-        // If no sort order is specified use the default
+
         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;
         }
 
-        // Get the database and run the query
+        // Opens the database object in "read" mode, since no writes need to be done.
         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-        Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
 
-        // Tell the cursor what uri to watch, so it knows when its source data changes
+        /*
+         * 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.
+     *
+     * @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)) {
-        case NOTES:
-        case LIVE_FOLDER_NOTES:
-            return Notes.CONTENT_TYPE;
 
-        case NOTE_ID:
-            return Notes.CONTENT_ITEM_TYPE;
-
-        default:
-            throw new IllegalArgumentException("Unknown URI " + uri);
-        }
-    }
-
-//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)) {
+            // If the pattern is for notes or live folders, returns the general content type.
             case NOTES:
             case LIVE_FOLDER_NOTES:
-                return null;
+                return NotePad.Notes.CONTENT_TYPE;
 
+            // If the pattern is for note IDs, returns the note ID content type.
             case NOTE_ID:
-                if (compareMimeTypes("text/plain", mimeTypeFilter)) {
-                    return new String[] { "text/plain" };
-                }
-                return null;
+                return NotePad.Notes.CONTENT_ITEM_TYPE;
 
+            // If the pattern doesn't match any permitted patterns, throws an exception.
             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)
 
+    /**
+     * 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) {
-        // Validate the requested uri
+
+        // 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());
 
-        // Make sure that the fields are all set
-        if (values.containsKey(NotePad.Notes.CREATED_DATE) == false) {
-            values.put(NotePad.Notes.CREATED_DATE, now);
+        // 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 (values.containsKey(NotePad.Notes.MODIFIED_DATE) == false) {
-            values.put(NotePad.Notes.MODIFIED_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 (values.containsKey(NotePad.Notes.TITLE) == false) {
+        // 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.TITLE, r.getString(android.R.string.untitled));
+            values.put(NotePad.Notes.COLUMN_NAME_TITLE, r.getString(android.R.string.untitled));
         }
 
-        if (values.containsKey(NotePad.Notes.NOTE) == false) {
-            values.put(NotePad.Notes.NOTE, "");
+        // 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();
-        long rowId = db.insert(NOTES_TABLE_NAME, Notes.NOTE, values);
+
+        // 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) {
-            Uri noteUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_URI, rowId);
+            // 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();
+
         int count;
+
+        // Does the delete based on the incoming URI pattern.
         switch (sUriMatcher.match(uri)) {
-        case NOTES:
-            count = db.delete(NOTES_TABLE_NAME, where, whereArgs);
-            break;
 
-        case NOTE_ID:
-            String noteId = uri.getPathSegments().get(1);
-            count = db.delete(NOTES_TABLE_NAME, Notes._ID + "=" + noteId
-                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
-            break;
+            // 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;
 
-        default:
-            throw new IllegalArgumentException("Unknown URI " + uri);
+                // 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:
+                // From the incoming URI, get the note ID
+                String noteId = uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION);
+
+                // If no where clause was passed in, uses the note ID column name
+                // for a column and the note ID for a value.
+                if (TextUtils.isEmpty(where)) {
+                    where = NotePad.Notes._ID + " = ?";
+                    whereArgs[0] = noteId;
+
+                } else {
+
+                    /*
+                     * If where clause columns were passed in, appends the note ID column name to
+                     * the list of columns using a replaceable parameter. This works even if the
+                     * other columns have actual values.
+                     */
+                    // Appends the note ID column name as an AND condition using a replaceable
+                    // parameter.
+                    where = where + " AND " + NotePad.Notes._ID + " = ?";
+
+                    // Appends the note ID value to the end of the where clause values.
+                    whereArgs[whereArgs.length] = noteId;
+                }
+
+                // Performs the delete.
+                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 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#insert(Uri, ContentValues)}
+     * 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.
+     * @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;
+
+        // Does the update based on the incoming URI pattern
         switch (sUriMatcher.match(uri)) {
-        case NOTES:
-            count = db.update(NOTES_TABLE_NAME, values, where, whereArgs);
-            break;
 
-        case NOTE_ID:
-            String noteId = uri.getPathSegments().get(1);
-            count = db.update(NOTES_TABLE_NAME, values, Notes._ID + "=" + noteId
-                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
-            break;
+            // If the incoming URI matches the general notes pattern, does the update based on
+            // the incoming data.
+            case NOTES:
 
-        default:
-            throw new IllegalArgumentException("Unknown URI " + uri);
+                // 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);
+
+                // If no where clause was passed in, uses the note ID column name
+                // for a column and the note ID for a value.
+                if (TextUtils.isEmpty(where)) {
+                    where = NotePad.Notes._ID + " = ?";
+                    whereArgs[0] = noteId;
+
+                    // If where clause columns were passed in, appends the note ID to the where
+                    // clause
+                } else {
+
+                    /*
+                     * Appends the note ID column name to the list of columns, with a replaceable
+                     * parameter. This will work even if the rest of the columns have been set with
+                     * actual values.
+                     */
+                    // Appends the note ID column name as an AND condition with a replaceable
+                    // parameter.
+                    where = where + " AND " + NotePad.Notes._ID + " = ?";
+
+                    // Appends the note ID value to the end of the where clause values.
+                    whereArgs[whereArgs.length] = noteId;
+                }
+
+                // Does the update.
+                // 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 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;
     }
 
-    static {
-        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-        sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES);
-        sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID);
-        sUriMatcher.addURI(NotePad.AUTHORITY, "live_folders/notes", LIVE_FOLDER_NOTES);
-
-        sNotesProjectionMap = new HashMap<String, String>();
-        sNotesProjectionMap.put(Notes._ID, Notes._ID);
-        sNotesProjectionMap.put(Notes.TITLE, Notes.TITLE);
-        sNotesProjectionMap.put(Notes.NOTE, Notes.NOTE);
-        sNotesProjectionMap.put(Notes.CREATED_DATE, Notes.CREATED_DATE);
-        sNotesProjectionMap.put(Notes.MODIFIED_DATE, Notes.MODIFIED_DATE);
-
-        // Support for Live Folders.
-        sLiveFolderProjectionMap = new HashMap<String, String>();
-        sLiveFolderProjectionMap.put(LiveFolders._ID, Notes._ID + " AS " +
-                LiveFolders._ID);
-        sLiveFolderProjectionMap.put(LiveFolders.NAME, Notes.TITLE + " AS " +
-                LiveFolders.NAME);
-        // Add more columns here for more robust Live Folders.
+    /**
+     * 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;
     }
 }
diff --git a/samples/NotePad/src/com/example/android/notepad/NotesList.java b/samples/NotePad/src/com/example/android/notepad/NotesList.java
index bbcb936..63d480e 100644
--- a/samples/NotePad/src/com/example/android/notepad/NotesList.java
+++ b/samples/NotePad/src/com/example/android/notepad/NotesList.java
@@ -16,14 +16,9 @@
 
 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;
@@ -38,217 +33,464 @@
 import android.widget.ListView;
 import android.widget.SimpleCursorAdapter;
 
+import com.example.android.notepad.NotePad;
+
 /**
  * Displays a list of notes. Will display notes from the {@link Uri}
- * provided in the intent if there is one, otherwise defaults to displaying the
- * contents of the {@link NotePadProvider}
+ * 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";
 
     // Menu item ids
     public static final int MENU_ITEM_DELETE = Menu.FIRST;
-    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;
+    public static final int MENU_ITEM_INSERT = Menu.FIRST + 1;
+
 
     /**
-     * The columns we are interested in from the database
+     * The columns needed by the cursor adapter
      */
     private static final String[] PROJECTION = new String[] {
-            Notes._ID, // 0
-            Notes.TITLE, // 1
+            NotePad.Notes._ID, // 0
+            NotePad.Notes.COLUMN_NAME_TITLE, // 1
     };
 
-    /** The index of the title column */
-    private static final int COLUMN_INDEX_TITLE = 1;
-    
-    private MenuItem mPasteItem;
-
+    /**
+     * 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 was given in the intent (because we were started
-        // as a MAIN activity), then use our default content provider.
+        /* 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(Notes.CONTENT_URI);
+            intent.setData(NotePad.Notes.CONTENT_URI);
         }
 
-        // Inform the list we provide context menus for items
-        getListView().setOnCreateContextMenuListener(this);
-        
-        // Perform a managed query. The Activity will handle closing and requerying the cursor
-        // when needed.
-        Cursor cursor = managedQuery(getIntent().getData(), PROJECTION, null, null,
-                Notes.DEFAULT_SORT_ORDER);
+        /*
+         * 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.
+         */
 
-        // Used to map notes entries from the database to views
-        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.noteslist_item, cursor,
-                new String[] { Notes.TITLE }, new int[] { android.R.id.text1 });
+        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) {
         super.onCreateOptionsMenu(menu);
 
-        // This is our one standard application action -- inserting a
-        // new note into the list.
-        menu.add(0, MENU_ITEM_INSERT, 0, R.string.menu_insert)
-                .setShortcut('3', 'a')
-                .setIcon(android.R.drawable.ic_menu_add);
+        // Adds an Insert menu item
+        MenuItem menuItem = menu.add(
+            Menu.NONE,                    // No menu group.
+            MENU_ITEM_INSERT,             // Unique ID for this item.
+            Menu.NONE,                    // No order within the group.
+            R.string.menu_insert          // Displayed text for the menu item.
+        );
 
-        // 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');
+        // Sets keyboard shortcuts for the menu item, either "3" or "a";
+        menuItem.setShortcut('3', 'a');
 
-        // Generate any additional actions that can be performed on the
+        // Sets the icon for the menu item
+        menuItem.setIcon(android.R.drawable.ic_menu_add);
+
+        // Generates 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.
+
+        /* Creates a new Intent with the same incoming data and no defined action.
+         * It also sets its category to ALTERNATIVE. This prepares the Intent as a place
+         * to group alternative options in the menu.
+         */
         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);
 
+        /* Creates a ComponentName from the current Context and this Activity object's
+         * class object.
+         */
+        ComponentName component = new ComponentName(this, NotesList.class);
+
+        /*
+         * Adds any other activities that want to be alternatives for this view. In effect,
+         * any application can add itself as an alternative on the options menu.
+         */
+        menu.addIntentOptions(
+            Menu.CATEGORY_ALTERNATIVE,   // Add the options to the Alternatives group
+            Menu.NONE,                   // Do not use a unique ID
+            Menu.NONE,                   // No need to order the options
+            component,                   // The ComponentName of the Activity making the request.
+                                         // This Activity is excluded from the list of alternatives.
+            null,                        // No specific items are listed first.
+            intent,                      // The Intent to resolve to, in effect, an Intent listing
+                                         // the alternatives
+            Menu.NONE,                   // no flags are needed
+            null                         // Since no specifics were used, so a menu item array is
+                                         // not needed.
+        );
+
+        // Returns true so that the menu is displayed.
         return true;
     }
 
+    /**
+     * Called when the user clicks the device's Menu button any time after the first time for
+     * this Activity. This modifies the standard options menu that was set up previously.
+     * See onCreateOptionsMenu().
+     * @param menu The existing Menu object for this Activity.
+     * @return True, always. The menu should be displayed.
+     */
     @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);
-        }
-
+        // Does the displayed list of notes have any items in it?
         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 there are notes in the list, assumes that one of them is selected.
+         * Generates the menu options that can be performed on the current selection,
+         * which includes options from this application plus actions from other
+         * applications that "registered" themselves using an intent filter for the
+         * NotePad.Notes.CONTENT_TYPE mimeType.
+         */
+
+        // Modifies the current menu
         if (haveItems) {
-            // This is the selected item.
+
+            /*
+             * Appends the selected item's row ID (note ID) to the the URI in the incoming Intent's
+             * data area. The incoming Intent at this point could only be GET_CONTENT, which means
+             * that the URI is for a single note, so this statement constructs a URI that will
+             * select a single note from the provider.
+             */
             Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());
 
-            // Build menu...  always starts with the EDIT action...
+            /*
+             * Builds the menu. The EDIT action is always the first action in the list, so
+             * a specifics array is required, but only needs one element.
+             *
+             */
+
+            // Creates an array of "specifics", Intents that must appear before all other options
             Intent[] specifics = new Intent[1];
+
+            // Sets the Intent of the specifics array to be EDIT on the note ID URI
             specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
+
+            // Creates an array of menu items. The result of mapping specifics to Intents is put
+            // into this array
             MenuItem[] items = new MenuItem[1];
 
-            // ... is followed by whatever other actions are available...
+            // Creates an Intent with no defined action and the note ID URI as data
             Intent intent = new Intent(null, uri);
-            intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
-            menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0,
-                    items);
 
-            // Give a shortcut to the edit action.
+            /* 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) {
+
+        // Gets the ID of the selected item
         switch (item.getItemId()) {
+
+        // If the menu item is Insert
         case MENU_ITEM_INSERT:
-            // Launch activity to insert a new item
+
+            /*
+             * 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 MENU_ITEM_PASTE:
-            // Launch activity to insert a new item
-            startActivity(new Intent(Intent.ACTION_PASTE, getIntent().getData()));
+
+            // Returns true, so that no more processing is done.
             return true;
         }
+
+        // If the menu item selected is not Insert, then this calls the regular processing to
+        // handle the item.
         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 option is 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) {
         AdapterView.AdapterContextMenuInfo info;
+
+        // Tries to get the position of the item in the ListView that was long-pressed.
         try {
-             info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+            // 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;
         }
 
-        // Setup the menu header
-        menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE));
+        // Finds the index of the title column in the Cursor
+        int titleIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
 
-        // Add a menu item to copy the note
-        menu.add(0, MENU_ITEM_COPY, 0, R.string.menu_copy);
+        // Sets the menu header to be the title of the selected note.
+        menu.setHeaderTitle(cursor.getString(titleIndex));
 
-        // Add a menu item to delete the note
-        menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_delete);
+        // Adds a menu item to the context menu
+        menu.add(
+            Menu.NONE,            // No grouping is needed for this menu
+            MENU_ITEM_DELETE,     // A unique ID for this menu item.
+            Menu.NONE,            // No ordering is necessary in this menu
+            R.string.menu_delete  // The resource ID for the string to display for this item.
+        );
     }
-        
+
+    /**
+     * This method is called when the user selects an item from the context menu
+     * (see onCreateContextMenu()). The only menu item that is actually handled is DELETE.
+     * 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 {
-             info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+            // 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;
         }
 
+        /*
+         * Gets the menu item's ID and compares it to known actions. The only action that is
+         * implemented is DELETE (it is set in onCreateContextMenu()). The switch statement is
+         * used to facilitate adding other actions in the future.
+         */
         switch (item.getItemId()) {
             case MENU_ITEM_DELETE: {
-                // Delete the note that the context menu is for
+                // Appends the selected note's ID to the URI sent with the incoming Intent.
                 Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
-                getContentResolver().delete(noteUri, null, null);
+
+                // 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;
             }
-//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)
         }
+
+        // For menu items other than DELETE, returns to the caller for further processing.
         return false;
     }
 
+    /**
+     * 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)) {
-            // The caller is waiting for us to return a note selected by
-            // the user.  The have clicked on one, so return it now.
+
+            // 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 {
-            // Launch activity to view/edit the currently selected item
+
+            // 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));
         }
     }
diff --git a/samples/NotePad/src/com/example/android/notepad/NotesLiveFolder.java b/samples/NotePad/src/com/example/android/notepad/NotesLiveFolder.java
index 00eb314..3310de6 100644
--- a/samples/NotePad/src/com/example/android/notepad/NotesLiveFolder.java
+++ b/samples/NotePad/src/com/example/android/notepad/NotesLiveFolder.java
@@ -18,48 +18,91 @@
 
 import android.app.Activity;
 import android.content.Intent;
-import android.net.Uri;
+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 {
     /**
-     * The URI for the Notes Live Folder content provider.
+     * 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).
      */
-    public static final Uri CONTENT_URI = Uri.parse("content://"
-            + NotePad.AUTHORITY + "/live_folders/notes");
-
-    public static final Uri NOTE_URI = Uri.parse("content://"
-            + NotePad.AUTHORITY + "/notes/#");
-
     @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)) {
-            // Build the live folder intent.
+            // Create a new Intent.
             final Intent liveFolderIntent = new Intent();
 
-            liveFolderIntent.setData(CONTENT_URI);
-            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME,
-                    getString(R.string.live_folder_name));
-            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
-                    Intent.ShortcutIconResource.fromContext(this,
-                            R.drawable.live_folder_notes));
-            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,
-                    LiveFolders.DISPLAY_MODE_LIST);
-            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT,
-                    new Intent(Intent.ACTION_EDIT, NOTE_URI));
+            /*
+             * 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);
 
-            // The result of this activity should be a live folder intent.
+            // 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();
+
     }
 }
diff --git a/samples/NotePad/src/com/example/android/notepad/TitleEditor.java b/samples/NotePad/src/com/example/android/notepad/TitleEditor.java
index 50d38e5..4406262 100644
--- a/samples/NotePad/src/com/example/android/notepad/TitleEditor.java
+++ b/samples/NotePad/src/com/example/android/notepad/TitleEditor.java
@@ -28,88 +28,154 @@
 import android.widget.EditText;
 
 /**
- * An activity that will edit the title of a note. Displays a floating
- * window with a text field.
+ * 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 implements View.OnClickListener {
 
     /**
-     * This is a special intent action that means "edit the title of a note".
+     * 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";
 
-    /**
-     * An array of the columns we are interested in.
-     */
+    // 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.TITLE, // 1
+            NotePad.Notes._ID,
+            NotePad.Notes.COLUMN_NAME_TITLE,
     };
-    /** Index of the title column */
+    // The position of the title column in a Cursor returned by the provider.
     private static final int COLUMN_INDEX_TITLE = 1;
 
-    /**
-     * Cursor which will provide access to the note whose title we are editing.
-     */
+    // A Cursor object that will contain the results of querying the provider for a note.
     private Cursor mCursor;
 
-    /**
-     * The EditText field from our UI. Keep track of this so we can extract the
-     * text when we are finished.
-     */
+    // An EditText object for preserving the edited title.
     private EditText mText;
 
-    /**
-     * The content URI to the note that's being edited.
-     */
+    // 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 uri of the note whose title we want to edit
+        // 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();
 
-        // Get a cursor to access the note
-        mCursor = managedQuery(mUri, PROJECTION, null, null, null);
+        /*
+         * 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.
+        );
 
-        // Set up click handlers for the text field and button
+        // Sets up a listener for the EditText. Gets the EditText by its ID, then sets its
+        // onClickListener to this Activity.
         mText = (EditText) this.findViewById(R.id.title);
         mText.setOnClickListener(this);
-        
+
+        // Sets up a listener for the OK button. Gets the Button by its ID, then sets its
+        // onClickListener to this Activity.
         Button b = (Button) findViewById(R.id.ok);
         b.setOnClickListener(this);
     }
 
+    /**
+     * 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();
 
-        // Initialize the text with the title column from the cursor
+        // 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) {
-            // Write the title back to the note 
+
+            // Creates a values map for updating the provider.
             ContentValues values = new ContentValues();
-            values.put(Notes.TITLE, mText.getText().toString());
-            getContentResolver().update(mUri, values, null, null);
+
+            // In the values map, sets the title to the current contents of the edit box.
+            values.put(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.
+            );
         }
     }
 
+    /**
+     * This method is called when the user clicks anywhere in the title text box.
+     *
+     * It calls finish(), which immediately triggers the onPause() method in this Activity. In
+     * turn, onPause() saves the text currently in the title text box to the note.
+     */
     public void onClick(View v) {
-        // When the user clicks, just finish this activity.
-        // onPause will be called, and we save our data there.
+        // Calls finish to force the Activity to shut down. In the lifecycle, this forces a call to
+        // onPause(), which saves the work the user has done.
         finish();
     }
 }
diff --git a/samples/NotePad/src/com/google/provider/NotePad.java b/samples/NotePad/src/com/google/provider/NotePad.java
deleted file mode 100644
index f8de69b..0000000
--- a/samples/NotePad/src/com/google/provider/NotePad.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/* 
- * 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.google.provider;
-
-import android.net.Uri;
-import android.provider.BaseColumns;
-
-/**
- * Convenience definitions for NotePadProvider
- */
-public final class NotePad {
-    /**
-     * Notes table
-     */
-    public static final class Notes implements BaseColumns {
-        /**
-         * The content:// style URL for this table
-         */
-        public static final Uri CONTENT_URI
-                = Uri.parse("content://com.google.provider.NotePad/notes");
-
-        /**
-         * The default sort order for this table
-         */
-        public static final String DEFAULT_SORT_ORDER = "modified DESC";
-
-        /**
-         * The title of the note
-         * <P>Type: TEXT</P>
-         */
-        public static final String TITLE = "title";
-
-        /**
-         * The note itself
-         * <P>Type: TEXT</P>
-         */
-        public static final String NOTE = "note";
-
-        /**
-         * The timestamp for when the note was created
-         * <P>Type: INTEGER (long)</P>
-         */
-        public static final String CREATED_DATE = "created";
-
-        /**
-         * The timestamp for when the note was last modified
-         * <P>Type: INTEGER (long)</P>
-         */
-        public static final String MODIFIED_DATE = "modified";
-    }
-}
diff --git a/samples/NotePad/tests/Android.mk b/samples/NotePad/tests/Android.mk
index 43efafc..1e4af66 100644
--- a/samples/NotePad/tests/Android.mk
+++ b/samples/NotePad/tests/Android.mk
@@ -5,7 +5,7 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_PACKAGE_NAME := NotePadTests
+LOCAL_PACKAGE_NAME := NotePadTest
 
 LOCAL_MODULE_TAGS := tests
 
diff --git a/samples/NotePad/tests/AndroidManifest.xml b/samples/NotePad/tests/AndroidManifest.xml
index afd502b..90860d1 100644
--- a/samples/NotePad/tests/AndroidManifest.xml
+++ b/samples/NotePad/tests/AndroidManifest.xml
@@ -4,29 +4,51 @@
      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.
 -->
+<!--
+    If you use this file as a template in your own test package, you must change
+    the package name from "com.android.example.notepad.test" to one that you own or have
+    control over.
+    Notice that the package name is not necessarily related to any of the Java package
+    identifiers used in this application.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.notepad.test"
+    android:versionCode="2"
+    android:versionName="2.0">
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
-      package="com.example.android.notepad.tests">
-    
-    <!-- We add an application tag here just so that we can indicate that
-         this package needs to link against the android.test library,
-         which is needed when building test cases. -->    
+    <!--
+        Defines an application element. The XML DTD for Android requires
+        the &lt;uses-library&gt; element to appear within an &lt;application&gt; element.
+        In all other respects, this manifest declares a test package rather than a
+        regular Android application.
+    -->
     <application>
+        <!--
+            Specifies a code library that Android must load along with this package.
+            The library contains the instrumented test runner.
+         -->
         <uses-library android:name="android.test.runner" />
     </application>
 
-  <instrumentation android:name="android.test.InstrumentationTestRunner"
-      android:targetPackage="com.example.android.notepad"
-      android:label="NotePad sample tests">
-  </instrumentation>  
-  
+    <!--
+        Specifies the test runner that will run the test cases in this package.
+        The android:name attribute specifies the test runner. The android:targetPackage specifies
+        the Android package name of the application under test.
+        In general, Android handles an instrumentation element by loading the class in the
+        android:name attribute as an Instrumentation object before loading the package in which the
+        instrumentation element occurs and before the package specified in android:targetPackage.
+     -->
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.example.android.notepad">
+    </instrumentation>
 </manifest>
diff --git a/samples/NotePad/tests/_index.html b/samples/NotePad/tests/_index.html
new file mode 100644
index 0000000..29a3d83
--- /dev/null
+++ b/samples/NotePad/tests/_index.html
@@ -0,0 +1,97 @@
+<p>
+    This sample contains a test package that demonstrates
+    <a href="../../../reference/android/content/ContentProvider.html">ContentProvider</a> testing
+    and <a href="../../../reference/android/app/Activity.html">Activity</a> testing. It is a test
+    of the <a href="../NotePad/index.html">Note Pad</a> sample application.
+</p>
+<h2>Test Package</h2>
+<p>
+    The test package project is located in the <code>tests/</code> subdirectory of the
+    Note Pad project. It provides an example of the structure of a test package project,
+    and it contains a example of a <a href="AndroidManifest.html">
+    <code>AndroidManifest.xml</code></a> file for a test package.
+    The package uses <a href="../../../reference/android/test/InstrumentationTestRunner.html">
+    <code>InstrumentationTestRunner</code></a> as its test runner.
+</p>
+<p>
+    The <a href="AndroidManifest.html">manifest</a> for this test package contains an
+    <code>&lt;instrumentation&gt;</code> element that links the test package
+    with the application under test. Specifically, the element's <code>android:name</code>
+    attribute specifies that Android should use <code>InstrumentationTestRunner</code> to
+    control the classes in this package. The <code>android:targetPackage</code> attribute
+    targets <code>com.android.example.notepad</code> as the Android package that contains the
+    application under test.
+</p>
+<h2>ContentProvider Test</h2>
+<p>
+    The ContentProvider test suite is contained in the single test case class
+    <a href="src/com/example/android/notepad/NotePadProviderTest.html">
+    <code>NotePadProviderTest</code></a>. This class extends
+    <a href="../../../reference/android/test/ProviderTestCase2.html"><code>ProviderTestCase2</code>
+    </a>, which extends the JUnit
+    <a href="../../../reference/junit/framework/TestCase.html"><code>TestCase</code></a> class and
+    also provides methods and objects that facilitate ContentProvider testing.
+</p>
+<p>
+    Although the sample test suite for ContentProvider testing uses a single test case class,
+    you may use as many test case classes as you wish, and each class can contain as many
+    test methods as you wish.
+</p>
+<h3>ContentProvider test methods</h3>
+<p>
+    <code>NotePadProviderTest</code></a> contains the following methods:
+</p>
+    <ul>
+        <li>
+            Test setup: The <code>setUp()</code> method re-initializes the test environment
+            (the test fixutre) before each test is run. The call to <code>super.setUp()</code>
+            creates a test context and test resolver, and creates a local instance of the
+            provider under test. By creating the provider, the super constructor also tests the
+            provider's <code>onCreate()</code> method, which is called during the provider's
+            initialization.
+        <p>
+            The <code>setUp()</code> method then gets a handle to the local provider's
+            underlying database. This is used to set up test data in succeeding tests.
+        </p>
+        </li>
+        <li>
+            Test tear down: The <code>tearDown()</code> method calls <code>super.tearDown()</code>,
+            which cleans up left-over object references to prevent memory leaks.
+        </li>
+        <li>
+            Test database creation: The <code>insertData()</code> helper method adds test data
+            to the local provider. Individual test methods call <code>insertData()</code>
+            when they need to set up a test fixture that contains valid data. This data is not
+            inserted during <code>setUp()</code>, because some tests expect an empty database.
+        </li>
+        <li>
+            Content provider unit tests: The test package shows how to unit
+            test a content provider's getType, query, insert, delete, and update
+            methods against all the content Uris that the provider offers.
+            Only a subset of all the possible unit tests is included.
+        </li>
+    </ul>
+<h2>Activity Test</h2>
+<p>
+    The Activity test suite is contained in the single test case class
+    <a href="src/com/example/android/notepad/NotePadActivityTest.html">
+    <code>NotePadActivityTest</code></a>. This class extends
+    <a href="../../../reference/android/test/ActivityInstrumentationTestCase2.html">
+    <code>ActivityInstrumentationTestCase2</code>, which provides the JUnit test framework and
+    also provides instrumentation methods that facilitate Activity testing.
+</p>
+<p>
+    The <code>NotePadActivityTest</code> test class contains a single assertion,
+    <code>testActivityTestCaseSetUpProperly()</code>. This assertion calls
+    <a href="../../../reference/android/test/ActivityInstrumentationTestCase2.html#getActivity()">
+    <code>Activity.getActivity()</code> and then fails if the result is <code>null</code>,
+    indicating that the test was unable to start the Activity under test.
+</p>
+<img
+    src="../images/sample_note.png"
+    alt="The user interface for a single note in the Note Pad sample app"
+    style="height:230px"/>
+<img
+    src="../images/sample_notepadtest_junit.png"
+    alt="Results of a successful run of the NotePadTest test package in Eclipse"
+    style="height:230px"/>
diff --git a/samples/NotePad/tests/build.properties b/samples/NotePad/tests/build.properties
deleted file mode 100644
index e0c39de..0000000
--- a/samples/NotePad/tests/build.properties
+++ /dev/null
@@ -1 +0,0 @@
-tested.project.dir=..
diff --git a/samples/NotePad/tests/src/com/example/android/notepad/NotePadTest.java b/samples/NotePad/tests/src/com/example/android/notepad/NotePadActivityTest.java
similarity index 70%
rename from samples/NotePad/tests/src/com/example/android/notepad/NotePadTest.java
rename to samples/NotePad/tests/src/com/example/android/notepad/NotePadActivityTest.java
index 80f71d2..c29da44 100644
--- a/samples/NotePad/tests/src/com/example/android/notepad/NotePadTest.java
+++ b/samples/NotePad/tests/src/com/example/android/notepad/NotePadActivityTest.java
@@ -19,18 +19,22 @@
 import android.test.ActivityInstrumentationTestCase2;
 
 import com.example.android.notepad.NotesList;
-
 /**
  * Make sure that the main launcher activity opens up properly, which will be
  * verified by {@link #testActivityTestCaseSetUpProperly}.
+ *
+ * To learn how to run an entire test package or one of its classes, please see
+ * "Testing in Eclipse, with ADT" or "Testing in Other IDEs" in the Developer Guide.
  */
-public class NotePadTest extends ActivityInstrumentationTestCase2<NotesList> {
+public class NotePadActivityTest extends ActivityInstrumentationTestCase2<NotesList> {
 
     /**
      * Creates an {@link ActivityInstrumentationTestCase2} for the {@link NotesList} activity.
      */
-    public NotePadTest() {
-        super(NotesList.class);
+    public NotePadActivityTest() {
+        // This constructor was deprecated in API level 8 (SDK 2.2) but is
+        // used here so that the package remains compatible with API level 3 (SDK 1.5)
+        super("com.example.android.notepad", NotesList.class);
     }
 
     /**
diff --git a/samples/NotePad/tests/src/com/example/android/notepad/NotePadProviderTest.java b/samples/NotePad/tests/src/com/example/android/notepad/NotePadProviderTest.java
new file mode 100644
index 0000000..4790e42
--- /dev/null
+++ b/samples/NotePad/tests/src/com/example/android/notepad/NotePadProviderTest.java
@@ -0,0 +1,585 @@
+/*
+ * 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.
+ */
+
+package com.example.android.notepad;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.test.ProviderTestCase2;
+import android.test.mock.MockContentResolver;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+/*
+ */
+/**
+ * This class tests the content provider for the Note Pad sample application.
+ *
+ * To learn how to run an entire test package or one of its classes, please see
+ * "Testing in Eclipse, with ADT" or "Testing in Other IDEs" in the Developer Guide.
+ */
+public class NotePadProviderTest extends ProviderTestCase2<NotePadProvider> {
+
+    // A URI that the provider does not offer, for testing error handling.
+    private static final Uri INVALID_URI =
+        Uri.withAppendedPath(NotePad.Notes.CONTENT_URI, "invalid");
+
+    // Contains a reference to the mocked content resolver for the provider under test.
+    private MockContentResolver mMockResolver;
+
+    // Contains an SQLite database, used as test data
+    private SQLiteDatabase mDb;
+
+    // Contains the test data, as an array of NoteInfo instances.
+    private final NoteInfo[] TEST_NOTES = {
+        new NoteInfo("Note0", "This is note 0"),
+        new NoteInfo("Note1", "This is note 1"),
+        new NoteInfo("Note2", "This is note 2"),
+        new NoteInfo("Note3", "This is note 3"),
+        new NoteInfo("Note4", "This is note 4"),
+        new NoteInfo("Note5", "This is note 5"),
+        new NoteInfo("Note6", "This is note 6"),
+        new NoteInfo("Note7", "This is note 7"),
+        new NoteInfo("Note8", "This is note 8"),
+        new NoteInfo("Note9", "This is note 9") };
+
+    // Number of milliseconds in one day (milliseconds * seconds * minutes * hours)
+    private static final long ONE_DAY_MILLIS = 1000 * 60 * 60 * 24;
+
+    // Number of milliseconds in one week
+    private static final long ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7;
+
+    // Creates a calendar object equal to January 1, 2010 at 12 midnight
+    private static final GregorianCalendar TEST_CALENDAR =
+        new GregorianCalendar(2010, Calendar.JANUARY, 1, 0, 0, 0);
+
+    // Stores a timestamp value, set to an arbitrary starting point
+    private final static long START_DATE = TEST_CALENDAR.getTimeInMillis();
+
+    /*
+     * Constructor for the test case class.
+     * Calls the super constructor with the class name of the provider under test and the
+     * authority name of the provider.
+     */
+    public NotePadProviderTest() {
+        super(NotePadProvider.class, NotePad.AUTHORITY);
+    }
+
+
+    /*
+     * Sets up the test environment before each test method. Creates a mock content resolver,
+     * gets the provider under test, and creates a new database for the provider.
+     */
+    @Override
+    protected void setUp() throws Exception {
+        // Calls the base class implementation of this method.
+        super.setUp();
+
+        // Gets the resolver for this test.
+        mMockResolver = getMockContentResolver();
+
+        /*
+         * Gets a handle to the database underlying the provider. Gets the provider instance
+         * created in super.setUp(), gets the DatabaseOpenHelper for the provider, and gets
+         * a database object from the helper.
+         */
+        mDb = getProvider().getOpenHelperForTest().getWritableDatabase();
+    }
+
+    /*
+     *  This method is called after each test method, to clean up the current fixture. Since
+     *  this sample test case runs in an isolated context, no cleanup is necessary.
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /*
+     * Sets up test data.
+     * The test data is in an SQL database. It is created in setUp() without any data,
+     * and populated in insertData if necessary.
+     */
+    private void insertData() {
+        // Creates an instance of the ContentValues map type expected by database insertions
+        ContentValues values = new ContentValues();
+
+        // Sets up test data
+        for (int index = 0; index < TEST_NOTES.length; index++) {
+
+            // Set the creation and modification date for the note
+            TEST_NOTES[index].setCreationDate(START_DATE + (index * ONE_DAY_MILLIS));
+            TEST_NOTES[index].setModificationDate(START_DATE + (index * ONE_WEEK_MILLIS));
+
+            // Adds a record to the database.
+            mDb.insertOrThrow(
+                NotePad.Notes.TABLE_NAME,             // the table name for the insert
+                NotePad.Notes.COLUMN_NAME_TITLE,      // column set to null if empty values map
+                TEST_NOTES[index].getContentValues()  // the values map to insert
+            );
+        }
+    }
+
+    /*
+     * Tests the provider's publicly available URIs. If the URI is not one that the provider
+     * understands, the provider should throw an exception. It also tests the provider's getType()
+     * method for each URI, which should return the MIME type associated with the URI.
+     */
+    public void testUriAndGetType() {
+        // Tests the MIME type for the notes table URI.
+        String mimeType = mMockResolver.getType(NotePad.Notes.CONTENT_URI);
+        assertEquals(NotePad.Notes.CONTENT_TYPE, mimeType);
+
+        // Tests the MIME type for the live folder URI.
+        mimeType = mMockResolver.getType(NotePad.Notes.LIVE_FOLDER_URI);
+        assertEquals(NotePad.Notes.CONTENT_TYPE, mimeType);
+
+        // Creates a URI with a pattern for note ids. The id doesn't have to exist.
+        Uri noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1);
+
+        // Gets the note ID URI MIME type.
+        mimeType = mMockResolver.getType(noteIdUri);
+        assertEquals(NotePad.Notes.CONTENT_ITEM_TYPE, mimeType);
+
+        // Tests an invalid URI. This should throw an IllegalArgumentException.
+        mimeType = mMockResolver.getType(INVALID_URI);
+    }
+
+    /*
+     * Tests the provider's public API for querying data in the table, using the URI for
+     * a dataset of records.
+     */
+    public void testQueriesOnNotesUri() {
+        // Defines a projection of column names to return for a query
+        final String[] TEST_PROJECTION = {
+            NotePad.Notes.COLUMN_NAME_TITLE,
+            NotePad.Notes.COLUMN_NAME_NOTE,
+            NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE
+        };
+
+        // Defines a selection column for the query. When the selection columns are passed
+        // to the query, the selection arguments replace the placeholders.
+        final String TITLE_SELECTION = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
+
+        // Defines the selection columns for a query.
+        final String SELECTION_COLUMNS =
+            TITLE_SELECTION + " OR " + TITLE_SELECTION + " OR " + TITLE_SELECTION;
+
+         // Defines the arguments for the selection columns.
+        final String[] SELECTION_ARGS = { "Note0", "Note1", "Note5" };
+
+         // Defines a query sort order
+        final String SORT_ORDER = NotePad.Notes.COLUMN_NAME_TITLE + " ASC";
+
+        // Query subtest 1.
+        // If there are no records in the table, the returned cursor from a query should be empty.
+        Cursor cursor = mMockResolver.query(
+            NotePad.Notes.CONTENT_URI,  // the URI for the main data table
+            null,                       // no projection, get all columns
+            null,                       // no selection criteria, get all records
+            null,                       // no selection arguments
+            null                        // use default sort order
+        );
+
+         // Asserts that the returned cursor contains no records
+        assertEquals(0, cursor.getCount());
+
+         // Query subtest 2.
+         // If the table contains records, the returned cursor from a query should contain records.
+
+        // Inserts the test data into the provider's underlying data source
+        insertData();
+
+        // Gets all the columns for all the rows in the table
+        cursor = mMockResolver.query(
+            NotePad.Notes.CONTENT_URI,  // the URI for the main data table
+            null,                       // no projection, get all columns
+            null,                       // no selection criteria, get all records
+            null,                       // no selection arguments
+            null                        // use default sort order
+        );
+
+        // Asserts that the returned cursor contains the same number of rows as the size of the
+        // test data array.
+        assertEquals(TEST_NOTES.length, cursor.getCount());
+
+        // Query subtest 3.
+        // A query that uses a projection should return a cursor with the same number of columns
+        // as the projection, with the same names, in the same order.
+        Cursor projectionCursor = mMockResolver.query(
+              NotePad.Notes.CONTENT_URI,  // the URI for the main data table
+              TEST_PROJECTION,            // get the title, note, and mod date columns
+              null,                       // no selection columns, get all the records
+              null,                       // no selection criteria
+              null                        // use default the sort order
+        );
+
+        // Asserts that the number of columns in the cursor is the same as in the projection
+        assertEquals(TEST_PROJECTION.length, projectionCursor.getColumnCount());
+
+        // Asserts that the names of the columns in the cursor and in the projection are the same.
+        // This also verifies that the names are in the same order.
+        assertEquals(TEST_PROJECTION[0], projectionCursor.getColumnName(0));
+        assertEquals(TEST_PROJECTION[1], projectionCursor.getColumnName(1));
+        assertEquals(TEST_PROJECTION[2], projectionCursor.getColumnName(2));
+
+        // Query subtest 4
+        // A query that uses selection criteria should return only those rows that match the
+        // criteria. Use a projection so that it's easy to get the data in a particular column.
+        projectionCursor = mMockResolver.query(
+            NotePad.Notes.CONTENT_URI, // the URI for the main data table
+            TEST_PROJECTION,           // get the title, note, and mod date columns
+            SELECTION_COLUMNS,         // select on the title column
+            SELECTION_ARGS,            // select titles "Note0", "Note1", or "Note5"
+            SORT_ORDER                 // sort ascending on the title column
+        );
+
+        // Asserts that the cursor has the same number of rows as the number of selection arguments
+        assertEquals(SELECTION_ARGS.length, projectionCursor.getCount());
+
+        int index = 0;
+
+        while (projectionCursor.moveToNext()) {
+
+            // Asserts that the selection argument at the current index matches the value of
+            // the title column (column 0) in the current record of the cursor
+            assertEquals(SELECTION_ARGS[index], projectionCursor.getString(0));
+
+            index++;
+        }
+
+        // Asserts that the index pointer is now the same as the number of selection arguments, so
+        // that the number of arguments tested is exactly the same as the number of rows returned.
+        assertEquals(SELECTION_ARGS.length, index);
+
+    }
+
+    /*
+     * Tests queries against the provider, using the note id URI. This URI encodes a single
+     * record ID. The provider should only return 0 or 1 record.
+     */
+    public void testQueriesOnNoteIdUri() {
+      // Defines the selection column for a query. The "?" is replaced by entries in the
+      // selection argument array
+      final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
+
+      // Defines the argument for the selection column.
+      final String[] SELECTION_ARGS = { "Note1" };
+
+      // A sort order for the query.
+      final String SORT_ORDER = NotePad.Notes.COLUMN_NAME_TITLE + " ASC";
+
+      // Creates a projection includes the note id column, so that note id can be retrieved.
+      final String[] NOTE_ID_PROJECTION = {
+           NotePad.Notes._ID,                 // The Notes class extends BaseColumns,
+                                              // which includes _ID as the column name for the
+                                              // record's id in the data model
+           NotePad.Notes.COLUMN_NAME_TITLE};  // The note's title
+
+      // Query subtest 1.
+      // Tests that a query against an empty table returns null.
+
+      // Constructs a URI that matches the provider's notes id URI pattern, using an arbitrary
+      // value of 1 as the note ID.
+      Uri noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1);
+
+      // Queries the table with the notes ID URI. This should return an empty cursor.
+      Cursor cursor = mMockResolver.query(
+          noteIdUri, // URI pointing to a single record
+          null,      // no projection, get all the columns for each record
+          null,      // no selection criteria, get all the records in the table
+          null,      // no need for selection arguments
+          null       // default sort, by ascending title
+      );
+
+      // Asserts that the cursor is null.
+      assertEquals(0,cursor.getCount());
+
+      // Query subtest 2.
+      // Tests that a query against a table containing records returns a single record whose ID
+      // is the one requested in the URI provided.
+
+      // Inserts the test data into the provider's underlying data source.
+      insertData();
+
+      // Queries the table using the URI for the full table.
+      cursor = mMockResolver.query(
+          NotePad.Notes.CONTENT_URI, // the base URI for the table
+          NOTE_ID_PROJECTION,        // returns the ID and title columns of rows
+          SELECTION_COLUMNS,         // select based on the title column
+          SELECTION_ARGS,            // select title of "Note1"
+          SORT_ORDER                 // sort order returned is by title, ascending
+      );
+
+      // Asserts that the cursor contains only one row.
+      assertEquals(1, cursor.getCount());
+
+      // Moves to the cursor's first row, and asserts that this did not fail.
+      assertTrue(cursor.moveToFirst());
+
+      // Saves the record's note ID.
+      int inputNoteId = cursor.getInt(0);
+
+      // Builds a URI based on the provider's content ID URI base and the saved note ID.
+      noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, inputNoteId);
+
+      // Queries the table using the content ID URI, which returns a single record with the
+      // specified note ID, matching the selection criteria provided.
+      cursor = mMockResolver.query(noteIdUri, // the URI for a single note
+          NOTE_ID_PROJECTION,                 // same projection, get ID and title columns
+          SELECTION_COLUMNS,                  // same selection, based on title column
+          SELECTION_ARGS,                     // same selection arguments, title = "Note1"
+          SORT_ORDER                          // same sort order returned, by title, ascending
+      );
+
+      // Asserts that the cursor contains only one row.
+      assertEquals(1, cursor.getCount());
+
+      // Moves to the cursor's first row, and asserts that this did not fail.
+      assertTrue(cursor.moveToFirst());
+
+      // Asserts that the note ID passed to the provider is the same as the note ID returned.
+      assertEquals(inputNoteId, cursor.getInt(0));
+    }
+
+    /*
+     *  Tests inserts into the data model.
+     */
+    public void testInserts() {
+        // Creates a new note instance with ID of 30.
+        NoteInfo note = new NoteInfo(
+            "Note30", // the note's title
+            "Test inserting a note" // the note's content
+        );
+
+        // Sets the note's creation and modification times
+        note.setCreationDate(START_DATE + (10 * ONE_DAY_MILLIS));
+        note.setModificationDate(START_DATE + (2 * ONE_WEEK_MILLIS));
+
+        // Insert subtest 1.
+        // Inserts a row using the new note instance.
+        // No assertion will be done. The insert() method either works or throws an Exception
+        Uri rowUri = mMockResolver.insert(
+            NotePad.Notes.CONTENT_URI,  // the main table URI
+            note.getContentValues()     // the map of values to insert as a new record
+        );
+
+        // Parses the returned URI to get the note ID of the new note. The ID is used in subtest 2.
+        long noteId = ContentUris.parseId(rowUri);
+
+        // Does a full query on the table. Since insertData() hasn't yet been called, the
+        // table should only contain the record just inserted.
+        Cursor cursor = mMockResolver.query(
+            NotePad.Notes.CONTENT_URI, // the main table URI
+            null,                      // no projection, return all the columns
+            null,                      // no selection criteria, return all the rows in the model
+            null,                      // no selection arguments
+            null                       // default sort order
+        );
+
+        // Asserts that there should be only 1 record.
+        assertEquals(1, cursor.getCount());
+
+        // Moves to the first (and only) record in the cursor and asserts that this worked.
+        assertTrue(cursor.moveToFirst());
+
+        // Since no projection was used, get the column indexes of the returned columns
+        int titleIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
+        int noteIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
+        int crdateIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_CREATE_DATE);
+        int moddateIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE);
+
+        // Tests each column in the returned cursor against the data that was inserted, comparing
+        // the field in the NoteInfo object to the data at the column index in the cursor.
+        assertEquals(note.title, cursor.getString(titleIndex));
+        assertEquals(note.note, cursor.getString(noteIndex));
+        assertEquals(note.createDate, cursor.getLong(crdateIndex));
+        assertEquals(note.modDate, cursor.getLong(moddateIndex));
+
+        // Insert subtest 2.
+        // Tests that we can't insert a record whose id value already exists.
+
+        // Defines a ContentValues object so that the test can add a note ID to it.
+        ContentValues values = note.getContentValues();
+
+        // Adds the note ID retrieved in subtest 1 to the ContentValues object.
+        values.put(NotePad.Notes._ID, (int) noteId);
+
+        // Tries to insert this record into the table. This should fail and drop into the
+        // catch block. If it succeeds, issue a failure message.
+        try {
+            rowUri = mMockResolver.insert(NotePad.Notes.CONTENT_URI, values);
+            fail("Expected insert failure for existing record but insert succeeded.");
+        } catch (Exception e) {
+          // succeeded, so do nothing.
+        }
+    }
+
+    /*
+     * Tests deletions from the data model.
+     */
+    public void testDeletes() {
+        // Subtest 1.
+        // Tries to delete a record from a data model that is empty.
+
+        // Sets the selection column to "title"
+        final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
+
+        // Sets the selection argument "Note0"
+        final String[] SELECTION_ARGS = { "Note0" };
+
+        // Tries to delete rows matching the selection criteria from the data model.
+        int rowsDeleted = mMockResolver.delete(
+            NotePad.Notes.CONTENT_URI, // the base URI of the table
+            SELECTION_COLUMNS,         // select based on the title column
+            SELECTION_ARGS             // select title = "Note0"
+        );
+
+        // Assert that the deletion did not work. The number of deleted rows should be zero.
+        assertEquals(0, rowsDeleted);
+
+        // Subtest 2.
+        // Tries to delete an existing record. Repeats the previous subtest, but inserts data first.
+
+        // Inserts data into the model.
+        insertData();
+
+        // Uses the same parameters to try to delete the row with title "Note0"
+        rowsDeleted = mMockResolver.delete(
+            NotePad.Notes.CONTENT_URI, // the base URI of the table
+            SELECTION_COLUMNS,         // same selection column, "title"
+            SELECTION_ARGS             // same selection arguments, title = "Note0"
+        );
+
+        // The number of deleted rows should be 1.
+        assertEquals(1, rowsDeleted);
+
+        // Tests that the record no longer exists. Tries to get it from the table, and
+        // asserts that nothing was returned.
+
+        // Queries the table with the same selection column and argument used to delete the row.
+        Cursor cursor = mMockResolver.query(
+            NotePad.Notes.CONTENT_URI, // the base URI of the table
+            null,                      // no projection, return all columns
+            SELECTION_COLUMNS,         // select based on the title column
+            SELECTION_ARGS,            // select title = "Note0"
+            null                       // use the default sort order
+        );
+
+        // Asserts that the cursor is empty since the record had already been deleted.
+        assertEquals(0, cursor.getCount());
+    }
+
+    /*
+     * Tests updates to the data model.
+     */
+    public void testUpdates() {
+        // Selection column for identifying a record in the data model.
+        final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
+
+        // Selection argument for the selection column.
+        final String[] selectionArgs = { "Note1" };
+
+        // Defines a map of column names and values
+        ContentValues values = new ContentValues();
+
+        // Subtest 1.
+        // Tries to update a record in an empty table.
+
+        // Sets up the update by putting the "note" column and a value into the values map.
+        values.put(NotePad.Notes.COLUMN_NAME_NOTE, "Testing an update with this string");
+
+        // Tries to update the table
+        int rowsUpdated = mMockResolver.update(
+            NotePad.Notes.CONTENT_URI,  // the URI of the data table
+            values,                     // a map of the updates to do (column title and value)
+            SELECTION_COLUMNS,           // select based on the title column
+            selectionArgs               // select "title = Note1"
+        );
+
+        // Asserts that no rows were updated.
+        assertEquals(0, rowsUpdated);
+
+        // Subtest 2.
+        // Builds the table, and then tries the update again using the same arguments.
+
+        // Inserts data into the model.
+        insertData();
+
+        //  Does the update again, using the same arguments as in subtest 1.
+        rowsUpdated = mMockResolver.update(
+            NotePad.Notes.CONTENT_URI,   // The URI of the data table
+            values,                      // the same map of updates
+            SELECTION_COLUMNS,            // same selection, based on the title column
+            selectionArgs                // same selection argument, to select "title = Note1"
+        );
+
+        // Asserts that only one row was updated. The selection criteria evaluated to
+        // "title = Note1", and the test data should only contain one row that matches that.
+        assertEquals(1, rowsUpdated);
+    }
+
+    // A utility for converting note data to a ContentValues map.
+    private static class NoteInfo {
+        String title;
+        String note;
+        long createDate;
+        long modDate;
+
+        /*
+         * Constructor for a NoteInfo instance. This class helps create a note and
+         * return its values in a ContentValues map expected by data model methods.
+         * The note's id is created automatically when it is inserted into the data model.
+         */
+        public NoteInfo(String t, String n) {
+            title = t;
+            note = n;
+            createDate = 0;
+            modDate = 0;
+        }
+
+        // Sets the creation date for a test note
+        public void setCreationDate(long c) {
+            createDate = c;
+        }
+
+        // Sets the modification date for a test note
+        public void setModificationDate(long m) {
+            modDate = m;
+        }
+
+        /*
+         * Returns a ContentValues instance (a map) for this NoteInfo instance. This is useful for
+         * inserting a NoteInfo into a database.
+         */
+        public ContentValues getContentValues() {
+            // Gets a new ContentValues object
+            ContentValues v = new ContentValues();
+
+            // Adds map entries for the user-controlled fields in the map
+            v.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
+            v.put(NotePad.Notes.COLUMN_NAME_NOTE, note);
+            v.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, createDate);
+            v.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, modDate);
+            return v;
+
+        }
+    }
+}