• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.notepad;
18 
19 import com.example.android.notepad.NotePad;
20 
21 import android.app.ListActivity;
22 import android.content.ClipboardManager;
23 import android.content.ClipData;
24 import android.content.ComponentName;
25 import android.content.ContentUris;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.util.Log;
32 import android.view.ContextMenu;
33 import android.view.Menu;
34 import android.view.MenuInflater;
35 import android.view.MenuItem;
36 import android.view.View;
37 import android.view.ContextMenu.ContextMenuInfo;
38 import android.widget.AdapterView;
39 import android.widget.ListView;
40 import android.widget.SimpleCursorAdapter;
41 
42 /**
43  * Displays a list of notes. Will display notes from the {@link Uri}
44  * provided in the incoming Intent if there is one, otherwise it defaults to displaying the
45  * contents of the {@link NotePadProvider}.
46  *
47  * NOTE: Notice that the provider operations in this Activity are taking place on the UI thread.
48  * This is not a good practice. It is only done here to make the code more readable. A real
49  * application should use the {@link android.content.AsyncQueryHandler} or
50  * {@link android.os.AsyncTask} object to perform operations asynchronously on a separate thread.
51  */
52 public class NotesList extends ListActivity {
53 
54     // For logging and debugging
55     private static final String TAG = "NotesList";
56 
57     /**
58      * The columns needed by the cursor adapter
59      */
60     private static final String[] PROJECTION = new String[] {
61             NotePad.Notes._ID, // 0
62             NotePad.Notes.COLUMN_NAME_TITLE, // 1
63     };
64 
65     /** The index of the title column */
66     private static final int COLUMN_INDEX_TITLE = 1;
67 
68     /**
69      * onCreate is called when Android starts this Activity from scratch.
70      */
71     @Override
onCreate(Bundle savedInstanceState)72     protected void onCreate(Bundle savedInstanceState) {
73         super.onCreate(savedInstanceState);
74 
75         // The user does not need to hold down the key to use menu shortcuts.
76         setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
77 
78         /* If no data is given in the Intent that started this Activity, then this Activity
79          * was started when the intent filter matched a MAIN action. We should use the default
80          * provider URI.
81          */
82         // Gets the intent that started this Activity.
83         Intent intent = getIntent();
84 
85         // If there is no data associated with the Intent, sets the data to the default URI, which
86         // accesses a list of notes.
87         if (intent.getData() == null) {
88             intent.setData(NotePad.Notes.CONTENT_URI);
89         }
90 
91         /*
92          * Sets the callback for context menu activation for the ListView. The listener is set
93          * to be this Activity. The effect is that context menus are enabled for items in the
94          * ListView, and the context menu is handled by a method in NotesList.
95          */
96         getListView().setOnCreateContextMenuListener(this);
97 
98         /* Performs a managed query. The Activity handles closing and requerying the cursor
99          * when needed.
100          *
101          * Please see the introductory note about performing provider operations on the UI thread.
102          */
103         Cursor cursor = managedQuery(
104             getIntent().getData(),            // Use the default content URI for the provider.
105             PROJECTION,                       // Return the note ID and title for each note.
106             null,                             // No where clause, return all records.
107             null,                             // No where clause, therefore no where column values.
108             NotePad.Notes.DEFAULT_SORT_ORDER  // Use the default sort order.
109         );
110 
111         /*
112          * The following two arrays create a "map" between columns in the cursor and view IDs
113          * for items in the ListView. Each element in the dataColumns array represents
114          * a column name; each element in the viewID array represents the ID of a View.
115          * The SimpleCursorAdapter maps them in ascending order to determine where each column
116          * value will appear in the ListView.
117          */
118 
119         // The names of the cursor columns to display in the view, initialized to the title column
120         String[] dataColumns = { NotePad.Notes.COLUMN_NAME_TITLE } ;
121 
122         // The view IDs that will display the cursor columns, initialized to the TextView in
123         // noteslist_item.xml
124         int[] viewIDs = { android.R.id.text1 };
125 
126         // Creates the backing adapter for the ListView.
127         SimpleCursorAdapter adapter
128             = new SimpleCursorAdapter(
129                       this,                             // The Context for the ListView
130                       R.layout.noteslist_item,          // Points to the XML for a list item
131                       cursor,                           // The cursor to get items from
132                       dataColumns,
133                       viewIDs
134               );
135 
136         // Sets the ListView's adapter to be the cursor adapter that was just created.
137         setListAdapter(adapter);
138     }
139 
140     /**
141      * Called when the user clicks the device's Menu button the first time for
142      * this Activity. Android passes in a Menu object that is populated with items.
143      *
144      * Sets up a menu that provides the Insert option plus a list of alternative actions for
145      * this Activity. Other applications that want to handle notes can "register" themselves in
146      * Android by providing an intent filter that includes the category ALTERNATIVE and the
147      * mimeTYpe NotePad.Notes.CONTENT_TYPE. If they do this, the code in onCreateOptionsMenu()
148      * will add the Activity that contains the intent filter to its list of options. In effect,
149      * the menu will offer the user other applications that can handle notes.
150      * @param menu A Menu object, to which menu items should be added.
151      * @return True, always. The menu should be displayed.
152      */
153     @Override
onCreateOptionsMenu(Menu menu)154     public boolean onCreateOptionsMenu(Menu menu) {
155         // Inflate menu from XML resource
156         MenuInflater inflater = getMenuInflater();
157         inflater.inflate(R.menu.list_options_menu, menu);
158 
159         // Generate any additional actions that can be performed on the
160         // overall list.  In a normal install, there are no additional
161         // actions found here, but this allows other applications to extend
162         // our menu with their own actions.
163         Intent intent = new Intent(null, getIntent().getData());
164         intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
165         menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
166                 new ComponentName(this, NotesList.class), null, intent, 0, null);
167 
168         return super.onCreateOptionsMenu(menu);
169     }
170 
171     @Override
onPrepareOptionsMenu(Menu menu)172     public boolean onPrepareOptionsMenu(Menu menu) {
173         super.onPrepareOptionsMenu(menu);
174 
175         // The paste menu item is enabled if there is data on the clipboard.
176         ClipboardManager clipboard = (ClipboardManager)
177                 getSystemService(Context.CLIPBOARD_SERVICE);
178 
179 
180         MenuItem mPasteItem = menu.findItem(R.id.menu_paste);
181 
182         // If the clipboard contains an item, enables the Paste option on the menu.
183         if (clipboard.hasPrimaryClip()) {
184             mPasteItem.setEnabled(true);
185         } else {
186             // If the clipboard is empty, disables the menu's Paste option.
187             mPasteItem.setEnabled(false);
188         }
189 
190         // Gets the number of notes currently being displayed.
191         final boolean haveItems = getListAdapter().getCount() > 0;
192 
193         // If there are any notes in the list (which implies that one of
194         // them is selected), then we need to generate the actions that
195         // can be performed on the current selection.  This will be a combination
196         // of our own specific actions along with any extensions that can be
197         // found.
198         if (haveItems) {
199 
200             // This is the selected item.
201             Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());
202 
203             // Creates an array of Intents with one element. This will be used to send an Intent
204             // based on the selected menu item.
205             Intent[] specifics = new Intent[1];
206 
207             // Sets the Intent in the array to be an EDIT action on the URI of the selected note.
208             specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
209 
210             // Creates an array of menu items with one element. This will contain the EDIT option.
211             MenuItem[] items = new MenuItem[1];
212 
213             // Creates an Intent with no specific action, using the URI of the selected note.
214             Intent intent = new Intent(null, uri);
215 
216             /* Adds the category ALTERNATIVE to the Intent, with the note ID URI as its
217              * data. This prepares the Intent as a place to group alternative options in the
218              * menu.
219              */
220             intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
221 
222             /*
223              * Add alternatives to the menu
224              */
225             menu.addIntentOptions(
226                 Menu.CATEGORY_ALTERNATIVE,  // Add the Intents as options in the alternatives group.
227                 Menu.NONE,                  // A unique item ID is not required.
228                 Menu.NONE,                  // The alternatives don't need to be in order.
229                 null,                       // The caller's name is not excluded from the group.
230                 specifics,                  // These specific options must appear first.
231                 intent,                     // These Intent objects map to the options in specifics.
232                 Menu.NONE,                  // No flags are required.
233                 items                       // The menu items generated from the specifics-to-
234                                             // Intents mapping
235             );
236                 // If the Edit menu item exists, adds shortcuts for it.
237                 if (items[0] != null) {
238 
239                     // Sets the Edit menu item shortcut to numeric "1", letter "e"
240                     items[0].setShortcut('1', 'e');
241                 }
242             } else {
243                 // If the list is empty, removes any existing alternative actions from the menu
244                 menu.removeGroup(Menu.CATEGORY_ALTERNATIVE);
245             }
246 
247         // Displays the menu
248         return true;
249     }
250 
251     /**
252      * This method is called when the user selects an option from the menu, but no item
253      * in the list is selected. If the option was INSERT, then a new Intent is sent out with action
254      * ACTION_INSERT. The data from the incoming Intent is put into the new Intent. In effect,
255      * this triggers the NoteEditor activity in the NotePad application.
256      *
257      * If the item was not INSERT, then most likely it was an alternative option from another
258      * application. The parent method is called to process the item.
259      * @param item The menu item that was selected by the user
260      * @return True, if the INSERT menu item was selected; otherwise, the result of calling
261      * the parent method.
262      */
263     @Override
onOptionsItemSelected(MenuItem item)264     public boolean onOptionsItemSelected(MenuItem item) {
265         switch (item.getItemId()) {
266         case R.id.menu_add:
267           /*
268            * Launches a new Activity using an Intent. The intent filter for the Activity
269            * has to have action ACTION_INSERT. No category is set, so DEFAULT is assumed.
270            * In effect, this starts the NoteEditor Activity in NotePad.
271            */
272            startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));
273            return true;
274         case R.id.menu_paste:
275           /*
276            * Launches a new Activity using an Intent. The intent filter for the Activity
277            * has to have action ACTION_PASTE. No category is set, so DEFAULT is assumed.
278            * In effect, this starts the NoteEditor Activity in NotePad.
279            */
280           startActivity(new Intent(Intent.ACTION_PASTE, getIntent().getData()));
281           return true;
282         default:
283             return super.onOptionsItemSelected(item);
284         }
285     }
286 
287     /**
288      * This method is called when the user context-clicks a note in the list. NotesList registers
289      * itself as the handler for context menus in its ListView (this is done in onCreate()).
290      *
291      * The only available options are COPY and DELETE.
292      *
293      * Context-click is equivalent to long-press.
294      *
295      * @param menu A ContexMenu object to which items should be added.
296      * @param view The View for which the context menu is being constructed.
297      * @param menuInfo Data associated with view.
298      * @throws ClassCastException
299      */
300     @Override
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)301     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
302 
303         // The data from the menu item.
304         AdapterView.AdapterContextMenuInfo info;
305 
306         // Tries to get the position of the item in the ListView that was long-pressed.
307         try {
308             // Casts the incoming data object into the type for AdapterView objects.
309             info = (AdapterView.AdapterContextMenuInfo) menuInfo;
310         } catch (ClassCastException e) {
311             // If the menu object can't be cast, logs an error.
312             Log.e(TAG, "bad menuInfo", e);
313             return;
314         }
315 
316         /*
317          * Gets the data associated with the item at the selected position. getItem() returns
318          * whatever the backing adapter of the ListView has associated with the item. In NotesList,
319          * the adapter associated all of the data for a note with its list item. As a result,
320          * getItem() returns that data as a Cursor.
321          */
322         Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
323 
324         // If the cursor is empty, then for some reason the adapter can't get the data from the
325         // provider, so returns null to the caller.
326         if (cursor == null) {
327             // For some reason the requested item isn't available, do nothing
328             return;
329         }
330 
331         // Inflate menu from XML resource
332         MenuInflater inflater = getMenuInflater();
333         inflater.inflate(R.menu.list_context_menu, menu);
334 
335         // Sets the menu header to be the title of the selected note.
336         menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE));
337 
338         // Append to the
339         // menu items for any other activities that can do stuff with it
340         // as well.  This does a query on the system for any activities that
341         // implement the ALTERNATIVE_ACTION for our data, adding a menu item
342         // for each one that is found.
343         Intent intent = new Intent(null, Uri.withAppendedPath(getIntent().getData(),
344                                         Integer.toString((int) info.id) ));
345         intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
346         menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
347                 new ComponentName(this, NotesList.class), null, intent, 0, null);
348     }
349 
350     /**
351      * This method is called when the user selects an item from the context menu
352      * (see onCreateContextMenu()). The only menu items that are actually handled are DELETE and
353      * COPY. Anything else is an alternative option, for which default handling should be done.
354      *
355      * @param item The selected menu item
356      * @return True if the menu item was DELETE, and no default processing is need, otherwise false,
357      * which triggers the default handling of the item.
358      * @throws ClassCastException
359      */
360     @Override
onContextItemSelected(MenuItem item)361     public boolean onContextItemSelected(MenuItem item) {
362         // The data from the menu item.
363         AdapterView.AdapterContextMenuInfo info;
364 
365         /*
366          * Gets the extra info from the menu item. When an note in the Notes list is long-pressed, a
367          * context menu appears. The menu items for the menu automatically get the data
368          * associated with the note that was long-pressed. The data comes from the provider that
369          * backs the list.
370          *
371          * The note's data is passed to the context menu creation routine in a ContextMenuInfo
372          * object.
373          *
374          * When one of the context menu items is clicked, the same data is passed, along with the
375          * note ID, to onContextItemSelected() via the item parameter.
376          */
377         try {
378             // Casts the data object in the item into the type for AdapterView objects.
379             info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
380         } catch (ClassCastException e) {
381 
382             // If the object can't be cast, logs an error
383             Log.e(TAG, "bad menuInfo", e);
384 
385             // Triggers default processing of the menu item.
386             return false;
387         }
388         // Appends the selected note's ID to the URI sent with the incoming Intent.
389         Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
390 
391         /*
392          * Gets the menu item's ID and compares it to known actions.
393          */
394         switch (item.getItemId()) {
395         case R.id.context_open:
396             // Launch activity to view/edit the currently selected item
397             startActivity(new Intent(Intent.ACTION_EDIT, noteUri));
398             return true;
399 //BEGIN_INCLUDE(copy)
400         case R.id.context_copy:
401             // Gets a handle to the clipboard service.
402             ClipboardManager clipboard = (ClipboardManager)
403                     getSystemService(Context.CLIPBOARD_SERVICE);
404 
405             // Copies the notes URI to the clipboard. In effect, this copies the note itself
406             clipboard.setPrimaryClip(ClipData.newUri(   // new clipboard item holding a URI
407                     getContentResolver(),               // resolver to retrieve URI info
408                     "Note",                             // label for the clip
409                     noteUri)                            // the URI
410             );
411 
412             // Returns to the caller and skips further processing.
413             return true;
414 //END_INCLUDE(copy)
415         case R.id.context_delete:
416 
417             // Deletes the note from the provider by passing in a URI in note ID format.
418             // Please see the introductory note about performing provider operations on the
419             // UI thread.
420             getContentResolver().delete(
421                 noteUri,  // The URI of the provider
422                 null,     // No where clause is needed, since only a single note ID is being
423                           // passed in.
424                 null      // No where clause is used, so no where arguments are needed.
425             );
426 
427             // Returns to the caller and skips further processing.
428             return true;
429         default:
430             return super.onContextItemSelected(item);
431         }
432     }
433 
434     /**
435      * This method is called when the user clicks a note in the displayed list.
436      *
437      * This method handles incoming actions of either PICK (get data from the provider) or
438      * GET_CONTENT (get or create data). If the incoming action is EDIT, this method sends a
439      * new Intent to start NoteEditor.
440      * @param l The ListView that contains the clicked item
441      * @param v The View of the individual item
442      * @param position The position of v in the displayed list
443      * @param id The row ID of the clicked item
444      */
445     @Override
onListItemClick(ListView l, View v, int position, long id)446     protected void onListItemClick(ListView l, View v, int position, long id) {
447 
448         // Constructs a new URI from the incoming URI and the row ID
449         Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
450 
451         // Gets the action from the incoming Intent
452         String action = getIntent().getAction();
453 
454         // Handles requests for note data
455         if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {
456 
457             // Sets the result to return to the component that called this Activity. The
458             // result contains the new URI
459             setResult(RESULT_OK, new Intent().setData(uri));
460         } else {
461 
462             // Sends out an Intent to start an Activity that can handle ACTION_EDIT. The
463             // Intent's data is the note ID URI. The effect is to call NoteEdit.
464             startActivity(new Intent(Intent.ACTION_EDIT, uri));
465         }
466     }
467 }
468