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