gt:         if (mCursor != null) {
context: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.example.android.notepad;
  
  import android.app.Activity;
  import android.content.ClipData;
  import android.content.ClipboardManager;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.ContentValues;
  import android.content.Context;
  import android.content.Intent;
  import android.content.res.Resources;
  import android.database.Cursor;
  import android.graphics.Canvas;
  import android.graphics.Paint;
  import android.graphics.Rect;
  import android.net.Uri;
  import android.os.Bundle;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.Menu;
  import android.view.MenuInflater;
  import android.view.MenuItem;
  import android.widget.EditText;
  
  /**
   * This Activity handles "editing" a note, where editing is responding to
   * {@link Intent#ACTION_VIEW} (request to view data), edit a note
   * {@link Intent#ACTION_EDIT}, create a note {@link Intent#ACTION_INSERT}, or
   * create a new note from the current contents of the clipboard {@link Intent#ACTION_PASTE}.
   *
   * NOTE: Notice that the provider operations in this Activity are taking place on the UI thread.
   * This is not a good practice. It is only done here to make the code more readable. A real
   * application should use the {@link android.content.AsyncQueryHandler}
   * or {@link android.os.AsyncTask} object to perform operations asynchronously on a separate thread.
   */
  public class NoteEditor extends Activity {
      // For logging and debugging purposes
      private static final String TAG = "NoteEditor";
  
      /*
       * Creates a projection that returns the note ID and the note contents.
       */
      private static final String[] PROJECTION =
          new String[] {
              NotePad.Notes._ID,
              NotePad.Notes.COLUMN_NAME_TITLE,
              NotePad.Notes.COLUMN_NAME_NOTE
      };
  
      // A label for the saved state of the activity
      private static final String ORIGINAL_CONTENT = "origContent";
  
      // This Activity can be started by more than one action. Each action is represented
      // as a "state" constant
      private static final int STATE_EDIT = 0;
      private static final int STATE_INSERT = 1;
  
      // Global mutable variables
      private int mState;
      private Uri mUri;
      private Cursor mCursor;
      private EditText mText;
      private String mOriginalContent;
  
      /**
       * Defines a custom EditText View that draws lines between each line of text that is displayed.
       */
      public static class LinedEditText extends EditText {
          private Rect mRect;
          private Paint mPaint;
  
          // This constructor is used by LayoutInflater
          public LinedEditText(Context context, AttributeSet attrs) {
              super(context, attrs);
  
              // Creates a Rect and a Paint object, and sets the style and color of the Paint object.
              mRect = new Rect();
              mPaint = new Paint();
              mPaint.setStyle(Paint.Style.STROKE);
              mPaint.setColor(0x800000FF);
          }
  
          /**
           * This is called to draw the LinedEditText object
           * @param canvas The canvas on which the background is drawn.
           */
          @Override
          protected void onDraw(Canvas canvas) {
  
              // Gets the number of lines of text in the View.
              int count = getLineCount();
  
              // Gets the global Rect and Paint objects
              Rect r = mRect;
              Paint paint = mPaint;
  
              /*
               * Draws one line in the rectangle for every line of text in the EditText
               */
              for (int i = 0; i < count; i++) {
  
                  // Gets the baseline coordinates for the current line of text
                  int baseline = getLineBounds(i, r);
  
                  /*
                   * Draws a line in the background from the left of the rectangle to the right,
                   * at a vertical position one dip below the baseline, using the "paint" object
                   * for details.
                   */
                  canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
              }
  
              // Finishes up by calling the parent method
              super.onDraw(canvas);
          }
      }
  
      /**
       * This method is called by Android when the Activity is first started. From the incoming
       * Intent, it determines what kind of editing is desired, and then does it.
       */
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
  
          /*
           * Creates an Intent to use when the Activity object's result is sent back to the
           * caller.
           */
          final Intent intent = getIntent();
  
          /*
           *  Sets up for the edit, based on the action specified for the incoming Intent.
           */
  
          // Gets the action that triggered the intent filter for this Activity
          final String action = intent.getAction();
  
          // For an edit action:
          if (Intent.ACTION_EDIT.equals(action)) {
  
              // Sets the Activity state to EDIT, and gets the URI for the data to be edited.
              mState = STATE_EDIT;
              mUri = intent.getData();
  
              // For an insert or paste action:
          } else if (Intent.ACTION_INSERT.equals(action)
                  || Intent.ACTION_PASTE.equals(action)) {
  
              // Sets the Activity state to INSERT, gets the general note URI, and inserts an
              // empty record in the provider
              mState = STATE_INSERT;
              mUri = getContentResolver().insert(intent.getData(), null);
  
              /*
               * If the attempt to insert the new note fails, shuts down this Activity. The
               * originating Activity receives back RESULT_CANCELED if it requested a result.
               * Logs that the insert failed.
               */
              if (mUri == null) {
  
                  // Writes the log identifier, a message, and the URI that failed.
                  Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
  
                  // Closes the activity.
                  finish();
                  return;
              }
  
              // Since the new entry was created, this sets the result to be returned
              // set the result to be returned.
              setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
  
          // If the action was other than EDIT or INSERT:
          } else {
  
              // Logs an error that the action was not understood, finishes the Activity, and
              // returns RESULT_CANCELED to an originating Activity.
              Log.e(TAG, "Unknown action, exiting");
              finish();
              return;
          }
  
          /*
           * Using the URI passed in with the triggering Intent, gets the note or notes in
           * the provider.
           * Note: This is being done on the UI thread. It will block the thread until the query
           * completes. In a sample app, going against a simple provider based on a local database,
           * the block will be momentary, but in a real app you should use
           * android.content.AsyncQueryHandler or android.os.AsyncTask.
           */
          mCursor = managedQuery(
              mUri,         // The URI that gets multiple notes from the provider.
              PROJECTION,   // A projection that returns the note ID and note content for each note.
              null,         // No "where" clause selection criteria.
              null,         // No "where" clause selection values.
              null          // Use the default sort order (modification date, descending)
          );
  
          // For a paste, initializes the data from clipboard.
          // (Must be done after mCursor is initialized.)
          if (Intent.ACTION_PASTE.equals(action)) {
              // Does the paste
              performPaste();
              // Switches the state to EDIT so the title can be modified.
              mState = STATE_EDIT;
          }
  
          // Sets the layout for this Activity. See res/layout/note_editor.xml
          setContentView(R.layout.note_editor);
  
          // Gets a handle to the EditText in the the layout.
          mText = (EditText) findViewById(R.id.note);
  
          /*
           * If this Activity had stopped previously, its state was written the ORIGINAL_CONTENT
           * location in the saved Instance state. This gets the state.
           */
          if (savedInstanceState != null) {
              mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
          }
      }
  
      /**
       * This method is called when the Activity is about to come to the foreground. This happens
       * when the Activity comes to the top of the task stack, OR when it is first starting.
       *
       * Moves to the first note in the list, sets an appropriate title for the action chosen by
       * the user, puts the note contents into the TextView, and saves the original text as a
       * backup.
       */
      @Override
      protected void onResume() {
          super.onResume();
  
          /*
           * mCursor is initialized, since onCreate() always precedes onResume for any running
           * process. This tests that it's not null, since it should always contain data.
           */
          if (mCursor != null) {
              // Requery in case something changed while paused (such as the title)
              mCursor.requery();
  
              /* Moves to the first record. Always call moveToFirst() before accessing data in
               * a Cursor for the first time. The semantics of using a Cursor are that when it is
               * created, its internal index is pointing to a "place" immediately before the first
               * record.
               */
              mCursor.moveToFirst();
  
              // Modifies the window title for the Activity according to the current Activity state.
              if (mState == STATE_EDIT) {
                  // Set the title of the Activity to include the note title
                  int colTitleIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
                  String title = mCursor.getString(colTitleIndex);
                  Resources res = getResources();
                  String text = String.format(res.getString(R.string.title_edit), title);
                  setTitle(text);
              // Sets the title to "create" for inserts
              } else if (mState == STATE_INSERT) {
                  setTitle(getText(R.string.title_create));
              }
  
              /*
               * onResume() may have been called after the Activity lost focus (was paused).
               * The user was either editing or creating a note when the Activity paused.
               * The Activity should re-display the text that had been retrieved previously, but
               * it should not move the cursor. This helps the user to continue editing or entering.
               */
  
              // Gets the note text from the Cursor and puts it in the TextView, but doesn't change
              // the text cursor's position.
              int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
              String note = mCursor.getString(colNoteIndex);
              mText.setTextKeepState(note);
  
              // Stores the original note text, to allow the user to revert changes.
              if (mOriginalContent == null) {
                  mOriginalContent = note;
              }
  
          /*
           * Something is wrong. The Cursor should always contain data. Report an error in the
           * note.
           */
          } else {
              setTitle(getText(R.string.error_title));
              mText.setText(getText(R.string.error_message));
          }
      }
  
      /**
       * This method is called when an Activity loses focus during its normal operation, and is then
       * later on killed. The Activity has a chance to save its state so that the system can restore
       * it.
       *
       * Notice that this method isn't a normal part of the Activity lifecycle. It won't be called
       * if the user simply navigates away from the Activity.
       */
      @Override
      protected void onSaveInstanceState(Bundle outState) {
          // Save away the original text, so we still have it if the activity
          // needs to be killed while paused.
          outState.putString(ORIGINAL_CONTENT, mOriginalContent);
      }
  
      /**
       * This method is called when the Activity loses focus.
       *
       * For Activity objects that edit information, onPause() may be the one place where changes are
       * saved. The Android application model is predicated on the idea that "save" and "exit" aren't
       * required actions. When users navigate away from an Activity, they shouldn't have to go back
       * to it to complete their work. The act of going away should save everything and leave the
       * Activity in a state where Android can destroy it if necessary.
       *
       * If the user hasn't done anything, then this deletes or clears out the note, otherwise it
       * writes the user's work to the provider.
       */
      @Override
      protected void onPause() {
          super.onPause();
  
          /*
           * Tests to see that the query operation didn't fail (see onCreate()). The Cursor object
           * will exist, even if no records were returned, unless the query failed because of some
           * exception or error.
           *
           */
          if (mCursor != null) {
  
              // Get the current note text.
              String text = mText.getText().toString();
              int length = text.length();
  
              /*
               * If the Activity is in the midst of finishing and there is no text in the current
               * note, returns a result of CANCELED to the caller, and deletes the note. This is done
               * even if the note was being edited, the assumption being that the user wanted to
               * "clear out" (delete) the note.
               */
              if (isFinishing() && (length == 0)) {
                  setResult(RESULT_CANCELED);
                  deleteNote();
  
                  /*
                   * Writes the edits to the provider. The note has been edited if an existing note was
                   * retrieved into the editor *or* if a new note was inserted. In the latter case,
                   * onCreate() inserted a new empty note into the provider, and it is this new note
                   * that is being edited.
                   */
              } else if (mState == STATE_EDIT) {
                  // Creates a map to contain the new values for the columns
                  updateNote(text, null);
              } else if (mState == STATE_INSERT) {
                  updateNote(text, text);
                  mState = STATE_EDIT;
            }
          }
      }
  
      /**
       * This method is called when the user clicks the device's Menu button the first time for
       * this Activity. Android passes in a Menu object that is populated with items.
       *
       * Builds the menus for editing and inserting, and adds in alternative actions that
       * registered themselves to handle the MIME types for this application.
       *
       * @param menu A Menu object to which items should be added.
       * @return True to display the menu.
       */
      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          // Inflate menu from XML resource
          MenuInflater inflater = getMenuInflater();
          inflater.inflate(R.menu.editor_options_menu, menu);
  
          // Only add extra menu items for a saved note 
          if (mState == STATE_EDIT) {
              // Append to the
              // menu items for any other activities that can do stuff with it
              // as well.  This does a query on the system for any activities that
              // implement the ALTERNATIVE_ACTION for our data, adding a menu item
              // for each one that is found.
              Intent intent = new Intent(null, mUri);
              intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
              menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
                      new ComponentName(this, NoteEditor.class), null, intent, 0, null);
          }
  
          return super.onCreateOptionsMenu(menu);
      }
  
      @Override
      public boolean onPrepareOptionsMenu(Menu menu) {
          // Check if note has changed and enable/disable the revert option
          int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
          String savedNote = mCursor.getString(colNoteIndex);
          String currentNote = mText.getText().toString();
          if (savedNote.equals(currentNote)) {
              menu.findItem(R.id.menu_revert).setVisible(false);
          } else {
              menu.findItem(R.id.menu_revert).setVisible(true);
          }
          return super.onPrepareOptionsMenu(menu);
      }
  
      /**
       * This method is called when a menu item is selected. Android passes in the selected item.
       * The switch statement in this method calls the appropriate method to perform the action the
       * user chose.
       *
       * @param item The selected MenuItem
       * @return True to indicate that the item was processed, and no further work is necessary. False
       * to proceed to further processing as indicated in the MenuItem object.
       */
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          // Handle all of the possible menu actions.
          switch (item.getItemId()) {
          case R.id.menu_save:
              String text = mText.getText().toString();
              updateNote(text, null);
              finish();
              break;
          case R.id.menu_delete:
              deleteNote();
              finish();
              break;
          case R.id.menu_revert:
              cancelNote();
              break;
          }
          return super.onOptionsItemSelected(item);
      }
  
  //BEGIN_INCLUDE(paste)
      /**
       * A helper method that replaces the note's data with the contents of the clipboard.
       */
      private final void performPaste() {
  
          // Gets a handle to the Clipboard Manager
          ClipboardManager clipboard = (ClipboardManager)
                  getSystemService(Context.CLIPBOARD_SERVICE);
  
          // Gets a content resolver instance
          ContentResolver cr = getContentResolver();
  
          // Gets the clipboard data from the clipboard
          ClipData clip = clipboard.getPrimaryClip();
          if (clip != null) {
  
              String text=null;
              String title=null;
  
              // Gets the first item from the clipboard data
              ClipData.Item item = clip.getItemAt(0);
  
              // Tries to get the item's contents as a URI pointing to a note
              Uri uri = item.getUri();
  
              // Tests to see that the item actually is an URI, and that the URI
              // is a content URI pointing to a provider whose MIME type is the same
              // as the MIME type supported by the Note pad provider.
              if (uri != null && NotePad.Notes.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) {
  
                  // The clipboard holds a reference to data with a note MIME type. This copies it.
                  Cursor orig = cr.query(
                          uri,            // URI for the content provider
                          PROJECTION,     // Get the columns referred to in the projection
                          null,           // No selection variables
                          null,           // No selection variables, so no criteria are needed
                          null            // Use the default sort order
                  );
  
                  // If the Cursor is not null, and it contains at least one record
                  // (moveToFirst() returns true), then this gets the note data from it.
                  if (orig != null) {
                      if (orig.moveToFirst()) {
                          int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
                          int colTitleIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
                          text = orig.getString(colNoteIndex);
                          title = orig.getString(colTitleIndex);
                      }
  
                      // Closes the cursor.
                      orig.close();
                  }
              }
  
              // If the contents of the clipboard wasn't a reference to a note, then
              // this converts whatever it is to text.
              if (text == null) {
                  text = item.coerceToText(this).toString();
              }
  
              // Updates the current note with the retrieved title and text.
              updateNote(text, title);
          }
      }
  //END_INCLUDE(paste)
  
      /**
       * Replaces the current note contents with the text and title provided as arguments.
       * @param text The new note contents to use.
       * @param title The new note title to use
       */
      private final void updateNote(String text, String title) {
  
          // Sets up a map to contain values to be updated in the provider.
          ContentValues values = new ContentValues();
          values.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, System.currentTimeMillis());
  
          // If the action is to insert a new note, this creates an initial title for it.
          if (mState == STATE_INSERT) {
  
              // If no title was provided as an argument, create one from the note text.
              if (title == null) {
    
                  // Get the note's length
                  int length = text.length();
  
                  // Sets the title by getting a substring of the text that is 31 characters long
                  // or the number of characters in the note plus one, whichever is smaller.
                  title = text.substring(0, Math.min(30, length));
    
                  // If the resulting length is more than 30 characters, chops off any
                  // trailing spaces
                  if (length > 30) {
                      int lastSpace = title.lastIndexOf(' ');
                      if (lastSpace > 0) {
                          title = title.substring(0, lastSpace);
                      }
                  }
              }
              // In the values map, sets the value of the title
              values.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
          } else if (title != null) {
              // In the values map, sets the value of the title
              values.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
          }
  
          // This puts the desired notes text into the map.
          values.put(NotePad.Notes.COLUMN_NAME_NOTE, text);
  
          /*
           * Updates the provider with the new values in the map. The ListView is updated
           * automatically. The provider sets this up by setting the notification URI for
           * query Cursor objects to the incoming URI. The content resolver is thus
           * automatically notified when the Cursor for the URI changes, and the UI is
           * updated.
           * Note: This is being done on the UI thread. It will block the thread until the
           * update completes. In a sample app, going against a simple provider based on a
           * local database, the block will be momentary, but in a real app you should use
           * android.content.AsyncQueryHandler or android.os.AsyncTask.
           */
          getContentResolver().update(
                  mUri,    // The URI for the record to update.
                  values,  // The map of column names and new values to apply to them.
                  null,    // No selection criteria are used, so no where columns are necessary.
                  null     // No where columns are used, so no where arguments are necessary.
              );
  
  
      }
  
      /**
       * This helper method cancels the work done on a note.  It deletes the note if it was
       * newly created, or reverts to the original text of the note i
       */
      private final void cancelNote() {
          if (mCursor != null) {
              if (mState == STATE_EDIT) {
                  // Put the original note text back into the database
                  mCursor.close();
                  mCursor = null;
                  ContentValues values = new ContentValues();
                  values.put(NotePad.Notes.COLUMN_NAME_NOTE, mOriginalContent);
                  getContentResolver().update(mUri, values, null, null);
              } else if (mState == STATE_INSERT) {
                  // We inserted an empty note, make sure to delete it
                  deleteNote();
              }
          }
          setResult(RESULT_CANCELED);
          finish();
      }
  
      /**
       * Take care of deleting a note.  Simply deletes the entry.
       */
      private final void deleteNote() {
  