| /* |
| * 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)); |
| } |
| } |
| } |