1/*
2 * Copyright (C) 2008 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
17package com.android.music;
18
19import android.app.ListActivity;
20import android.content.AsyncQueryHandler;
21import android.content.ContentUris;
22import android.content.Context;
23import android.content.Intent;
24import android.database.CharArrayBuffer;
25import android.database.Cursor;
26import android.media.AudioManager;
27import android.media.MediaPlayer;
28import android.media.RingtoneManager;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Parcelable;
32import android.provider.MediaStore;
33import android.text.TextUtils;
34import android.util.Log;
35import android.view.Menu;
36import android.view.MenuItem;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.Window;
40import android.view.animation.AnimationUtils;
41import android.widget.ImageView;
42import android.widget.ListView;
43import android.widget.RadioButton;
44import android.widget.SectionIndexer;
45import android.widget.SimpleCursorAdapter;
46import android.widget.TextView;
47
48import java.io.IOException;
49import java.text.Collator;
50import java.util.Formatter;
51import java.util.Locale;
52
53/**
54 * Activity allowing the user to select a music track on the device, and
55 * return it to its caller.  The music picker user interface is fairly
56 * extensive, providing information about each track like the music
57 * application (title, author, album, duration), as well as the ability to
58 * previous tracks and sort them in different orders.
59 *
60 * <p>This class also illustrates how you can load data from a content
61 * provider asynchronously, providing a good UI while doing so, perform
62 * indexing of the content for use inside of a {@link FastScrollView}, and
63 * perform filtering of the data as the user presses keys.
64 */
65public class MusicPicker extends ListActivity
66        implements View.OnClickListener, MediaPlayer.OnCompletionListener,
67        MusicUtils.Defs {
68    static final boolean DBG = false;
69    static final String TAG = "MusicPicker";
70
71    /** Holds the previous state of the list, to restore after the async
72     * query has completed. */
73    static final String LIST_STATE_KEY = "liststate";
74    /** Remember whether the list last had focus for restoring its state. */
75    static final String FOCUS_KEY = "focused";
76    /** Remember the last ordering mode for restoring state. */
77    static final String SORT_MODE_KEY = "sortMode";
78
79    /** Arbitrary number, doesn't matter since we only do one query type. */
80    static final int MY_QUERY_TOKEN = 42;
81
82    /** Menu item to sort the music list by track title. */
83    static final int TRACK_MENU = Menu.FIRST;
84    /** Menu item to sort the music list by album title. */
85    static final int ALBUM_MENU = Menu.FIRST+1;
86    /** Menu item to sort the music list by artist name. */
87    static final int ARTIST_MENU = Menu.FIRST+2;
88
89    /** These are the columns in the music cursor that we are interested in. */
90    static final String[] CURSOR_COLS = new String[] {
91            MediaStore.Audio.Media._ID,
92            MediaStore.Audio.Media.TITLE,
93            MediaStore.Audio.Media.TITLE_KEY,
94            MediaStore.Audio.Media.DATA,
95            MediaStore.Audio.Media.ALBUM,
96            MediaStore.Audio.Media.ARTIST,
97            MediaStore.Audio.Media.ARTIST_ID,
98            MediaStore.Audio.Media.DURATION,
99            MediaStore.Audio.Media.TRACK
100    };
101
102    /** Formatting optimization to avoid creating many temporary objects. */
103    static StringBuilder sFormatBuilder = new StringBuilder();
104    /** Formatting optimization to avoid creating many temporary objects. */
105    static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
106    /** Formatting optimization to avoid creating many temporary objects. */
107    static final Object[] sTimeArgs = new Object[5];
108
109    /** Uri to the directory of all music being displayed. */
110    Uri mBaseUri;
111
112    /** This is the adapter used to display all of the tracks. */
113    TrackListAdapter mAdapter;
114    /** Our instance of QueryHandler used to perform async background queries. */
115    QueryHandler mQueryHandler;
116
117    /** Used to keep track of the last scroll state of the list. */
118    Parcelable mListState = null;
119    /** Used to keep track of whether the list last had focus. */
120    boolean mListHasFocus;
121
122    /** The current cursor on the music that is being displayed. */
123    Cursor mCursor;
124    /** The actual sort order the user has selected. */
125    int mSortMode = -1;
126    /** SQL order by string describing the currently selected sort order. */
127    String mSortOrder;
128
129    /** Container of the in-screen progress indicator, to be able to hide it
130     * when done loading the initial cursor. */
131    View mProgressContainer;
132    /** Container of the list view hierarchy, to be able to show it when done
133     * loading the initial cursor. */
134    View mListContainer;
135    /** Set to true when the list view has been shown for the first time. */
136    boolean mListShown;
137
138    /** View holding the okay button. */
139    View mOkayButton;
140    /** View holding the cancel button. */
141    View mCancelButton;
142
143    /** Which track row ID the user has last selected. */
144    long mSelectedId = -1;
145    /** Completel Uri that the user has last selected. */
146    Uri mSelectedUri;
147
148    /** If >= 0, we are currently playing a track for preview, and this is its
149     * row ID. */
150    long mPlayingId = -1;
151
152    /** This is used for playing previews of the music files. */
153    MediaPlayer mMediaPlayer;
154
155    /**
156     * A special implementation of SimpleCursorAdapter that knows how to bind
157     * our cursor data to our list item structure, and takes care of other
158     * advanced features such as indexing and filtering.
159     */
160    class TrackListAdapter extends SimpleCursorAdapter
161            implements SectionIndexer {
162        final ListView mListView;
163
164        private final StringBuilder mBuilder = new StringBuilder();
165        private final String mUnknownArtist;
166        private final String mUnknownAlbum;
167
168        private int mIdIdx;
169        private int mTitleIdx;
170        private int mArtistIdx;
171        private int mAlbumIdx;
172        private int mDurationIdx;
173
174        private boolean mLoading = true;
175        private int mIndexerSortMode;
176        private MusicAlphabetIndexer mIndexer;
177
178        class ViewHolder {
179            TextView line1;
180            TextView line2;
181            TextView duration;
182            RadioButton radio;
183            ImageView play_indicator;
184            CharArrayBuffer buffer1;
185            char [] buffer2;
186        }
187
188        TrackListAdapter(Context context, ListView listView, int layout,
189                String[] from, int[] to) {
190            super(context, layout, null, from, to);
191            mListView = listView;
192            mUnknownArtist = context.getString(R.string.unknown_artist_name);
193            mUnknownAlbum = context.getString(R.string.unknown_album_name);
194        }
195
196        /**
197         * The mLoading flag is set while we are performing a background
198         * query, to avoid displaying the "No music" empty view during
199         * this time.
200         */
201        public void setLoading(boolean loading) {
202            mLoading = loading;
203        }
204
205        @Override
206        public boolean isEmpty() {
207            if (mLoading) {
208                // We don't want the empty state to show when loading.
209                return false;
210            } else {
211                return super.isEmpty();
212            }
213        }
214
215        @Override
216        public View newView(Context context, Cursor cursor, ViewGroup parent) {
217            View v = super.newView(context, cursor, parent);
218            ViewHolder vh = new ViewHolder();
219            vh.line1 = (TextView) v.findViewById(R.id.line1);
220            vh.line2 = (TextView) v.findViewById(R.id.line2);
221            vh.duration = (TextView) v.findViewById(R.id.duration);
222            vh.radio = (RadioButton) v.findViewById(R.id.radio);
223            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
224            vh.buffer1 = new CharArrayBuffer(100);
225            vh.buffer2 = new char[200];
226            v.setTag(vh);
227            return v;
228        }
229
230        @Override
231        public void bindView(View view, Context context, Cursor cursor) {
232            ViewHolder vh = (ViewHolder) view.getTag();
233
234            cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
235            vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
236
237            int secs = cursor.getInt(mDurationIdx) / 1000;
238            if (secs == 0) {
239                vh.duration.setText("");
240            } else {
241                vh.duration.setText(MusicUtils.makeTimeString(context, secs));
242            }
243
244            final StringBuilder builder = mBuilder;
245            builder.delete(0, builder.length());
246
247            String name = cursor.getString(mAlbumIdx);
248            if (name == null || name.equals("<unknown>")) {
249                builder.append(mUnknownAlbum);
250            } else {
251                builder.append(name);
252            }
253            builder.append('\n');
254            name = cursor.getString(mArtistIdx);
255            if (name == null || name.equals("<unknown>")) {
256                builder.append(mUnknownArtist);
257            } else {
258                builder.append(name);
259            }
260            int len = builder.length();
261            if (vh.buffer2.length < len) {
262                vh.buffer2 = new char[len];
263            }
264            builder.getChars(0, len, vh.buffer2, 0);
265            vh.line2.setText(vh.buffer2, 0, len);
266
267            // Update the checkbox of the item, based on which the user last
268            // selected.  Note that doing it this way means we must have the
269            // list view update all of its items when the selected item
270            // changes.
271            final long id = cursor.getLong(mIdIdx);
272            vh.radio.setChecked(id == mSelectedId);
273            if (DBG) Log.v(TAG, "Binding id=" + id + " sel=" + mSelectedId
274                    + " playing=" + mPlayingId + " cursor=" + cursor);
275
276            // Likewise, display the "now playing" icon if this item is
277            // currently being previewed for the user.
278            ImageView iv = vh.play_indicator;
279            if (id == mPlayingId) {
280                iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
281                iv.setVisibility(View.VISIBLE);
282            } else {
283                iv.setVisibility(View.GONE);
284            }
285        }
286
287        /**
288         * This method is called whenever we receive a new cursor due to
289         * an async query, and must take care of plugging the new one in
290         * to the adapter.
291         */
292        @Override
293        public void changeCursor(Cursor cursor) {
294            super.changeCursor(cursor);
295            if (DBG) Log.v(TAG, "Setting cursor to: " + cursor
296                    + " from: " + MusicPicker.this.mCursor);
297
298            MusicPicker.this.mCursor = cursor;
299
300            if (cursor != null) {
301                // Retrieve indices of the various columns we are interested in.
302                mIdIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
303                mTitleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
304                mArtistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
305                mAlbumIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
306                mDurationIdx = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
307
308                // If the sort mode has changed, or we haven't yet created an
309                // indexer one, then create a new one that is indexing the
310                // appropriate column based on the sort mode.
311                if (mIndexerSortMode != mSortMode || mIndexer == null) {
312                    mIndexerSortMode = mSortMode;
313                    int idx = mTitleIdx;
314                    switch (mIndexerSortMode) {
315                        case ARTIST_MENU:
316                            idx = mArtistIdx;
317                            break;
318                        case ALBUM_MENU:
319                            idx = mAlbumIdx;
320                            break;
321                    }
322                    mIndexer = new MusicAlphabetIndexer(cursor, idx,
323                            getResources().getString(R.string.fast_scroll_alphabet));
324
325                // If we have a valid indexer, but the cursor has changed since
326                // its last use, then point it to the current cursor.
327                } else {
328                    mIndexer.setCursor(cursor);
329                }
330            }
331
332            // Ensure that the list is shown (and initial progress indicator
333            // hidden) in case this is the first cursor we have gotten.
334            makeListShown();
335        }
336
337        /**
338         * This method is called from a background thread by the list view
339         * when the user has typed a letter that should result in a filtering
340         * of the displayed items.  It returns a Cursor, when will then be
341         * handed to changeCursor.
342         */
343        @Override
344        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
345            if (DBG) Log.v(TAG, "Getting new cursor...");
346            return doQuery(true, constraint.toString());
347        }
348
349        public int getPositionForSection(int section) {
350            Cursor cursor = getCursor();
351            if (cursor == null) {
352                // No cursor, the section doesn't exist so just return 0
353                return 0;
354            }
355
356            return mIndexer.getPositionForSection(section);
357        }
358
359        public int getSectionForPosition(int position) {
360            return 0;
361        }
362
363        public Object[] getSections() {
364            if (mIndexer != null) {
365                return mIndexer.getSections();
366            }
367            return null;
368        }
369    }
370
371    /**
372     * This is our specialization of AsyncQueryHandler applies new cursors
373     * to our state as they become available.
374     */
375    private final class QueryHandler extends AsyncQueryHandler {
376        public QueryHandler(Context context) {
377            super(context.getContentResolver());
378        }
379
380        @Override
381        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
382            if (!isFinishing()) {
383                // Update the adapter: we are no longer loading, and have
384                // a new cursor for it.
385                mAdapter.setLoading(false);
386                mAdapter.changeCursor(cursor);
387                setProgressBarIndeterminateVisibility(false);
388
389                // Now that the cursor is populated again, it's possible to restore the list state
390                if (mListState != null) {
391                    getListView().onRestoreInstanceState(mListState);
392                    if (mListHasFocus) {
393                        getListView().requestFocus();
394                    }
395                    mListHasFocus = false;
396                    mListState = null;
397                }
398            } else {
399                cursor.close();
400            }
401        }
402    }
403
404    /** Called when the activity is first created. */
405    @Override
406    public void onCreate(Bundle icicle) {
407        super.onCreate(icicle);
408
409        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
410
411        int sortMode = TRACK_MENU;
412        if (icicle == null) {
413            mSelectedUri = getIntent().getParcelableExtra(
414                    RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
415        } else {
416            mSelectedUri = (Uri)icicle.getParcelable(
417                    RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
418            // Retrieve list state. This will be applied after the
419            // QueryHandler has run
420            mListState = icicle.getParcelable(LIST_STATE_KEY);
421            mListHasFocus = icicle.getBoolean(FOCUS_KEY);
422            sortMode = icicle.getInt(SORT_MODE_KEY, sortMode);
423        }
424        if (Intent.ACTION_GET_CONTENT.equals(getIntent().getAction())) {
425            mBaseUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
426        } else {
427            mBaseUri = getIntent().getData();
428            if (mBaseUri == null) {
429                Log.w("MusicPicker", "No data URI given to PICK action");
430                finish();
431                return;
432            }
433        }
434
435        setContentView(R.layout.music_picker);
436
437        mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
438
439        final ListView listView = getListView();
440
441        listView.setItemsCanFocus(false);
442
443        mAdapter = new TrackListAdapter(this, listView,
444                R.layout.music_picker_item, new String[] {},
445                new int[] {});
446
447        setListAdapter(mAdapter);
448
449        listView.setTextFilterEnabled(true);
450
451        // We manually save/restore the listview state
452        listView.setSaveEnabled(false);
453
454        mQueryHandler = new QueryHandler(this);
455
456        mProgressContainer = findViewById(R.id.progressContainer);
457        mListContainer = findViewById(R.id.listContainer);
458
459        mOkayButton = findViewById(R.id.okayButton);
460        mOkayButton.setOnClickListener(this);
461        mCancelButton = findViewById(R.id.cancelButton);
462        mCancelButton.setOnClickListener(this);
463
464        // If there is a currently selected Uri, then try to determine who
465        // it is.
466        if (mSelectedUri != null) {
467            Uri.Builder builder = mSelectedUri.buildUpon();
468            String path = mSelectedUri.getEncodedPath();
469            int idx = path.lastIndexOf('/');
470            if (idx >= 0) {
471                path = path.substring(0, idx);
472            }
473            builder.encodedPath(path);
474            Uri baseSelectedUri = builder.build();
475            if (DBG) Log.v(TAG, "Selected Uri: " + mSelectedUri);
476            if (DBG) Log.v(TAG, "Selected base Uri: " + baseSelectedUri);
477            if (DBG) Log.v(TAG, "Base Uri: " + mBaseUri);
478            if (baseSelectedUri.equals(mBaseUri)) {
479                // If the base Uri of the selected Uri is the same as our
480                // content's base Uri, then use the selection!
481                mSelectedId = ContentUris.parseId(mSelectedUri);
482            }
483        }
484
485        setSortMode(sortMode);
486    }
487
488    @Override public void onRestart() {
489        super.onRestart();
490        doQuery(false, null);
491    }
492
493    @Override public boolean onOptionsItemSelected(MenuItem item) {
494        if (setSortMode(item.getItemId())) {
495            return true;
496        }
497        return super.onOptionsItemSelected(item);
498    }
499
500    @Override public boolean onCreateOptionsMenu(Menu menu) {
501        super.onCreateOptionsMenu(menu);
502        menu.add(Menu.NONE, TRACK_MENU, Menu.NONE, R.string.sort_by_track);
503        menu.add(Menu.NONE, ALBUM_MENU, Menu.NONE, R.string.sort_by_album);
504        menu.add(Menu.NONE, ARTIST_MENU, Menu.NONE, R.string.sort_by_artist);
505        return true;
506    }
507
508    @Override protected void onSaveInstanceState(Bundle icicle) {
509        super.onSaveInstanceState(icicle);
510        // Save list state in the bundle so we can restore it after the
511        // QueryHandler has run
512        icicle.putParcelable(LIST_STATE_KEY, getListView().onSaveInstanceState());
513        icicle.putBoolean(FOCUS_KEY, getListView().hasFocus());
514        icicle.putInt(SORT_MODE_KEY, mSortMode);
515    }
516
517    @Override public void onPause() {
518        super.onPause();
519        stopMediaPlayer();
520    }
521
522    @Override public void onStop() {
523        super.onStop();
524
525        // We don't want the list to display the empty state, since when we
526        // resume it will still be there and show up while the new query is
527        // happening. After the async query finishes in response to onResume()
528        // setLoading(false) will be called.
529        mAdapter.setLoading(true);
530        mAdapter.changeCursor(null);
531    }
532
533    /**
534     * Changes the current sort order, building the appropriate query string
535     * for the selected order.
536     */
537    boolean setSortMode(int sortMode) {
538        if (sortMode != mSortMode) {
539            switch (sortMode) {
540                case TRACK_MENU:
541                    mSortMode = sortMode;
542                    mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
543                    doQuery(false, null);
544                    return true;
545                case ALBUM_MENU:
546                    mSortMode = sortMode;
547                    mSortOrder = MediaStore.Audio.Media.ALBUM_KEY + " ASC, "
548                            + MediaStore.Audio.Media.TRACK + " ASC, "
549                            + MediaStore.Audio.Media.TITLE_KEY + " ASC";
550                    doQuery(false, null);
551                    return true;
552                case ARTIST_MENU:
553                    mSortMode = sortMode;
554                    mSortOrder = MediaStore.Audio.Media.ARTIST_KEY + " ASC, "
555                            + MediaStore.Audio.Media.ALBUM_KEY + " ASC, "
556                            + MediaStore.Audio.Media.TRACK + " ASC, "
557                            + MediaStore.Audio.Media.TITLE_KEY + " ASC";
558                    doQuery(false, null);
559                    return true;
560            }
561
562        }
563        return false;
564    }
565
566    /**
567     * The first time this is called, we hide the large progress indicator
568     * and show the list view, doing fade animations between them.
569     */
570    void makeListShown() {
571        if (!mListShown) {
572            mListShown = true;
573            mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
574                    this, android.R.anim.fade_out));
575            mProgressContainer.setVisibility(View.GONE);
576            mListContainer.startAnimation(AnimationUtils.loadAnimation(
577                    this, android.R.anim.fade_in));
578            mListContainer.setVisibility(View.VISIBLE);
579        }
580    }
581
582    /**
583     * Common method for performing a query of the music database, called for
584     * both top-level queries and filtering.
585     *
586     * @param sync If true, this query should be done synchronously and the
587     * resulting cursor returned.  If false, it will be done asynchronously and
588     * null returned.
589     * @param filterstring If non-null, this is a filter to apply to the query.
590     */
591    Cursor doQuery(boolean sync, String filterstring) {
592        // Cancel any pending queries
593        mQueryHandler.cancelOperation(MY_QUERY_TOKEN);
594
595        StringBuilder where = new StringBuilder();
596        where.append(MediaStore.Audio.Media.TITLE + " != ''");
597
598        // We want to show all audio files, even recordings.  Enforcing the
599        // following condition would hide recordings.
600        //where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
601
602        Uri uri = mBaseUri;
603        if (!TextUtils.isEmpty(filterstring)) {
604            uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filterstring)).build();
605        }
606
607        if (sync) {
608            try {
609                return getContentResolver().query(uri, CURSOR_COLS,
610                        where.toString(), null, mSortOrder);
611            } catch (UnsupportedOperationException ex) {
612            }
613        } else {
614            mAdapter.setLoading(true);
615            setProgressBarIndeterminateVisibility(true);
616            mQueryHandler.startQuery(MY_QUERY_TOKEN, null, uri, CURSOR_COLS,
617                    where.toString(), null, mSortOrder);
618        }
619        return null;
620    }
621
622    @Override protected void onListItemClick(ListView l, View v, int position,
623            long id) {
624        mCursor.moveToPosition(position);
625        if (DBG) Log.v(TAG, "Click on " + position + " (id=" + id
626                + ", cursid="
627                + mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID))
628                + ") in cursor " + mCursor
629                + " adapter=" + l.getAdapter());
630        setSelected(mCursor);
631    }
632
633    void setSelected(Cursor c) {
634        Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
635        long newId = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID));
636        mSelectedUri = ContentUris.withAppendedId(uri, newId);
637
638        mSelectedId = newId;
639        if (newId != mPlayingId || mMediaPlayer == null) {
640            stopMediaPlayer();
641            mMediaPlayer = new MediaPlayer();
642            try {
643                mMediaPlayer.setDataSource(this, mSelectedUri);
644                mMediaPlayer.setOnCompletionListener(this);
645                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
646                mMediaPlayer.prepare();
647                mMediaPlayer.start();
648                mPlayingId = newId;
649                getListView().invalidateViews();
650            } catch (IOException e) {
651                Log.w("MusicPicker", "Unable to play track", e);
652            }
653        } else if (mMediaPlayer != null) {
654            stopMediaPlayer();
655            getListView().invalidateViews();
656        }
657    }
658
659    public void onCompletion(MediaPlayer mp) {
660        if (mMediaPlayer == mp) {
661            mp.stop();
662            mp.release();
663            mMediaPlayer = null;
664            mPlayingId = -1;
665            getListView().invalidateViews();
666        }
667    }
668
669    void stopMediaPlayer() {
670        if (mMediaPlayer != null) {
671            mMediaPlayer.stop();
672            mMediaPlayer.release();
673            mMediaPlayer = null;
674            mPlayingId = -1;
675        }
676    }
677
678    public void onClick(View v) {
679        switch (v.getId()) {
680            case R.id.okayButton:
681                if (mSelectedId >= 0) {
682                    setResult(RESULT_OK, new Intent().setData(mSelectedUri));
683                    finish();
684                }
685                break;
686
687            case R.id.cancelButton:
688                finish();
689                break;
690        }
691    }
692}
693