blob: 39be81ca252eb86633381304fafa0b120b909017 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.notepad;
import com.example.android.notepad.NotePad;
import android.app.ListActivity;
import android.content.ClipboardManager;
import android.content.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;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
/**
* Displays a list of notes. Will display notes from the {@link Uri}
* provided in the incoming Intent if there is one, otherwise it defaults to displaying the
* contents of the {@link NotePadProvider}.
*
* NOTE: Notice that the provider operations in this Activity are taking place on the UI thread.
* This is not a good practice. It is only done here to make the code more readable. A real
* application should use the {@link android.content.AsyncQueryHandler} or
* {@link android.os.AsyncTask} object to perform operations asynchronously on a separate thread.
*/
public class NotesList extends ListActivity {
// For logging and debugging
private static final String TAG = "NotesList";
// 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;
/**
* The columns needed by the cursor adapter
*/
private static final String[] PROJECTION = new String[] {
NotePad.Notes._ID, // 0
NotePad.Notes.COLUMN_NAME_TITLE, // 1
};
/** The index of the title column */
private static final int COLUMN_INDEX_TITLE = 1;
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 is given in the Intent that started this Activity, then this Activity
* was started when the intent filter matched a MAIN action. We should use the default
* provider URI.
*/
// Gets the intent that started this Activity.
Intent intent = getIntent();
// If there is no data associated with the Intent, sets the data to the default URI, which
// accesses a list of notes.
if (intent.getData() == null) {
intent.setData(NotePad.Notes.CONTENT_URI);
}
/*
* Sets the callback for context menu activation for the ListView. The listener is set
* to be this Activity. The effect is that context menus are enabled for items in the
* ListView, and the context menu is handled by a method in NotesList.
*/
getListView().setOnCreateContextMenuListener(this);
/* Performs a managed query. The Activity handles closing and requerying the cursor
* when needed.
*
* Please see the introductory note about performing provider operations on the UI thread.
*/
Cursor cursor = managedQuery(
getIntent().getData(), // Use the default content URI for the provider.
PROJECTION, // Return the note ID and title for each note.
null, // No where clause, return all records.
null, // No where clause, therefore no where column values.
NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order.
);
/*
* The following two arrays create a "map" between columns in the cursor and view IDs
* for items in the ListView. Each element in the dataColumns array represents
* a column name; each element in the viewID array represents the ID of a View.
* The SimpleCursorAdapter maps them in ascending order to determine where each column
* value will appear in the ListView.
*/
// The names of the cursor columns to display in the view, initialized to the title column
String[] dataColumns = { NotePad.Notes.COLUMN_NAME_TITLE } ;
// The view IDs that will display the cursor columns, initialized to the TextView in
// noteslist_item.xml
int[] viewIDs = { android.R.id.text1 };
// Creates the backing adapter for the ListView.
SimpleCursorAdapter adapter
= new SimpleCursorAdapter(
this, // The Context for the ListView
R.layout.noteslist_item, // Points to the XML for a list item
cursor, // The cursor to get items from
dataColumns,
viewIDs
);
// Sets the ListView's adapter to be the cursor adapter that was just created.
setListAdapter(adapter);
}
/**
* Called when the user clicks the device's Menu button the first time for
* this Activity. Android passes in a Menu object that is populated with items.
*
* Sets up a menu that provides the Insert option plus a list of alternative actions for
* this Activity. Other applications that want to handle notes can "register" themselves in
* Android by providing an intent filter that includes the category ALTERNATIVE and the
* mimeTYpe NotePad.Notes.CONTENT_TYPE. If they do this, the code in onCreateOptionsMenu()
* will add the Activity that contains the intent filter to its list of options. In effect,
* the menu will offer the user other applications that can handle notes.
* @param menu A Menu object, to which menu items should be added.
* @return True, always. The menu should be displayed.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// The parent method creates the default method with the standard system items.
super.onCreateOptionsMenu(menu);
// 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.
);
// Sets keyboard shortcuts for the menu item, either "3" or "a";
menuItem.setShortcut('3', 'a');
// Sets the icon for the menu item
menuItem.setIcon(android.R.drawable.ic_menu_add);
// If there is currently data in the clipboard, this adds a PASTE menu item to the menu
// so that the user can paste in the data.
mPasteItem = menu.add(
Menu.NONE, // No menu group.
MENU_ITEM_PASTE, // A unique ID for this item
Menu.NONE, // No menu order
R.string.menu_paste // The displayed text
).setShortcut('4', 'p'); // Set the keyboard shortcuts for this new item.
// 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);
/* 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;
}
@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 the clipboard contains an item, enables the Paste option on the menu.
if (clipboard.hasPrimaryClip()) {
mPasteItem.setEnabled(true);
} else {
// If the clipboard is empty, disables the menu's Paste option.
mPasteItem.setEnabled(false);
}
// Gets the number of notes currently being displayed.
final boolean haveItems = getListAdapter().getCount() > 0;
// If there are any notes in the list (which implies that one of
// them is selected), then we need to generate the actions that
// can be performed on the current selection. This will be a combination
// of our own specific actions along with any extensions that can be
// found.
if (haveItems) {
// This is the selected item.
Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());
// Creates an array of Intents with one element. This will be used to send an Intent
// based on the selected menu item.
Intent[] specifics = new Intent[1];
// Sets the Intent in the array to be an EDIT action on the URI of the selected note.
specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
// Creates an array of menu items with one element. This will contain the EDIT option.
MenuItem[] items = new MenuItem[1];
// Creates an Intent with no specific action, using the URI of the selected note.
Intent intent = new Intent(null, uri);
/* Adds the category ALTERNATIVE to the Intent, with the note ID URI as its
* data. This prepares the Intent as a place to group alternative options in the
* menu.
*/
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
/*
* Add alternatives to the menu
*/
menu.addIntentOptions(
Menu.CATEGORY_ALTERNATIVE, // Add the Intents as options in the alternatives group.
Menu.NONE, // A unique item ID is not required.
Menu.NONE, // The alternatives don't need to be in order.
null, // The caller's name is not excluded from the group.
specifics, // These specific options must appear first.
intent, // These Intent objects map to the options in specifics.
Menu.NONE, // No flags are required.
items // The menu items generated from the specifics-to-
// Intents mapping
);
// If the Edit menu item exists, adds shortcuts for it.
if (items[0] != null) {
// Sets the Edit menu item shortcut to numeric "1", letter "e"
items[0].setShortcut('1', 'e');
}
} else {
// If the list is empty, removes any existing alternative actions from the menu
menu.removeGroup(Menu.CATEGORY_ALTERNATIVE);
}
// Displays the menu
return true;
}
/**
* This method is called when the user selects an option from the menu, but no item
* in the list is selected. If the option was INSERT, then a new Intent is sent out with action
* ACTION_INSERT. The data from the incoming Intent is put into the new Intent. In effect,
* this triggers the NoteEditor activity in the NotePad application.
*
* If the item was not INSERT, then most likely it was an alternative option from another
* application. The parent method is called to process the item.
* @param item The menu item that was selected by the user
* @return True, if the INSERT menu item was selected; otherwise, the result of calling
* the parent method.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Gets the ID of the selected item
switch (item.getItemId()) {
// If the menu item is Insert
case MENU_ITEM_INSERT:
/*
* 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;
// If the menu item is Paste
case MENU_ITEM_PASTE:
/*
* Launches a new Activity using an Intent. The intent filter for the Activity
* has to have action ACTION_PASTE. No category is set, so DEFAULT is assumed.
* In effect, this starts the NoteEditor Activity in NotePad.
*/
startActivity(new Intent(Intent.ACTION_PASTE, getIntent().getData()));
return true;
}
// If the menu item selected is not Insert or Paste, 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 options are COPY and DELETE.
*
* Context-click is equivalent to long-press.
*
* @param menu A ContexMenu object to which items should be added.
* @param view The View for which the context menu is being constructed.
* @param menuInfo Data associated with view.
* @throws ClassCastException
*/
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
// The data from the menu item.
AdapterView.AdapterContextMenuInfo info;
// Tries to get the position of the item in the ListView that was long-pressed.
try {
// Casts the incoming data object into the type for AdapterView objects.
info = (AdapterView.AdapterContextMenuInfo) menuInfo;
} catch (ClassCastException e) {
// If the menu object can't be cast, logs an error.
Log.e(TAG, "bad menuInfo", e);
return;
}
/*
* Gets the data associated with the item at the selected position. getItem() returns
* whatever the backing adapter of the ListView has associated with the item. In NotesList,
* the adapter associated all of the data for a note with its list item. As a result,
* getItem() returns that data as a Cursor.
*/
Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
// If the cursor is empty, then for some reason the adapter can't get the data from the
// provider, so returns null to the caller.
if (cursor == null) {
// For some reason the requested item isn't available, do nothing
return;
}
// Sets the menu header to be the title of the selected note.
menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE));
// Adds a menu item to copy the note to the context menu
menu.add(
Menu.NONE, // No grouping is needed for this menu
MENU_ITEM_COPY, // A unique ID for this menu item.
Menu.NONE, // No ordering is necessary in this menu
R.string.menu_copy // The resource ID for the string to display for this item.
);
// Adds a menu item to delete the note 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 items that are actually handled are DELETE and
* COPY. Anything else is an alternative option, for which default handling should be done.
*
* @param item The selected menu item
* @return True if the menu item was DELETE, and no default processing is need, otherwise false,
* which triggers the default handling of the item.
* @throws ClassCastException
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
// The data from the menu item.
AdapterView.AdapterContextMenuInfo info;
/*
* Gets the extra info from the menu item. When an note in the Notes list is long-pressed, a
* context menu appears. The menu items for the menu automatically get the data
* associated with the note that was long-pressed. The data comes from the provider that
* backs the list.
*
* The note's data is passed to the context menu creation routine in a ContextMenuInfo
* object.
*
* When one of the context menu items is clicked, the same data is passed, along with the
* note ID, to onContextItemSelected() via the item parameter.
*/
try {
// Casts the data object in the item into the type for AdapterView objects.
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
} catch (ClassCastException e) {
// If the object can't be cast, logs an error
Log.e(TAG, "bad menuInfo", e);
// Triggers default processing of the menu item.
return false;
}
/*
* Gets the menu item's ID and compares it to known actions. The only actions that are
* implemented are COPY and DELETE (set in onCreateContextMenu()).
*/
switch (item.getItemId()) {
// Deletes the selected note
case MENU_ITEM_DELETE: {
// Appends the selected note's ID to the URI sent with the incoming Intent.
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
// 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)
// Copies the selected note to the clipboard
case MENU_ITEM_COPY: {
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
// Appends the selected note's ID to the URI sent with the incoming Intent.
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
// Copies the notes URI to the clipboard. In effect, this copies the note itself
clipboard.setPrimaryClip(
new ClippedData( // creates a new clipboard item
null, // no visible label
null, // no visible icon
new ClippedData.Item(noteUri) // A clipboard Item that is a URI.
)
);
// Returns to the caller and skips further processing.
return true;
}
//END_INCLUDE(copy)
}
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)) {
// Sets the result to return to the component that called this Activity. The
// result contains the new URI
setResult(RESULT_OK, new Intent().setData(uri));
} else {
// Sends out an Intent to start an Activity that can handle ACTION_EDIT. The
// Intent's data is the note ID URI. The effect is to call NoteEdit.
startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}
}