• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.music;
18 
19 import android.app.ListActivity;
20 import android.content.AsyncQueryHandler;
21 import android.content.ContentUris;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.database.CharArrayBuffer;
25 import android.database.Cursor;
26 import android.media.AudioManager;
27 import android.media.MediaPlayer;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Parcelable;
32 import android.provider.MediaStore;
33 import android.text.TextUtils;
34 import android.util.Log;
35 import android.view.Menu;
36 import android.view.MenuItem;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.view.Window;
40 import android.view.animation.AnimationUtils;
41 import android.widget.ImageView;
42 import android.widget.ListView;
43 import android.widget.RadioButton;
44 import android.widget.SectionIndexer;
45 import android.widget.SimpleCursorAdapter;
46 import android.widget.TextView;
47 
48 import java.io.IOException;
49 import java.text.Collator;
50 import java.util.Formatter;
51 import 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  */
65 public class MusicPicker
66         extends ListActivity implements View.OnClickListener, MediaPlayer.OnCompletionListener {
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 
TrackListAdapter(Context context, ListView listView, int layout, String[] from, int[] to)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          */
setLoading(boolean loading)192         public void setLoading(boolean loading) {
193             mLoading = loading;
194         }
195 
196         @Override
isEmpty()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
newView(Context context, Cursor cursor, ViewGroup parent)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
bindView(View view, Context context, Cursor cursor)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,
266                         "Binding id=" + id + " sel=" + mSelectedId + " playing=" + mPlayingId
267                                 + " cursor=" + cursor);
268 
269             // Likewise, display the "now playing" icon if this item is
270             // currently being previewed for the user.
271             ImageView iv = vh.play_indicator;
272             if (id == mPlayingId) {
273                 iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
274                 iv.setVisibility(View.VISIBLE);
275             } else {
276                 iv.setVisibility(View.GONE);
277             }
278         }
279 
280         /**
281          * This method is called whenever we receive a new cursor due to
282          * an async query, and must take care of plugging the new one in
283          * to the adapter.
284          */
285         @Override
changeCursor(Cursor cursor)286         public void changeCursor(Cursor cursor) {
287             super.changeCursor(cursor);
288             if (DBG)
289                 Log.v(TAG, "Setting cursor to: " + cursor + " from: " + MusicPicker.this.mCursor);
290 
291             MusicPicker.this.mCursor = cursor;
292 
293             if (cursor != null) {
294                 // Retrieve indices of the various columns we are interested in.
295                 mIdIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
296                 mTitleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
297                 mArtistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
298                 mAlbumIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
299                 mDurationIdx = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
300 
301                 // If the sort mode has changed, or we haven't yet created an
302                 // indexer one, then create a new one that is indexing the
303                 // appropriate column based on the sort mode.
304                 if (mIndexerSortMode != mSortMode || mIndexer == null) {
305                     mIndexerSortMode = mSortMode;
306                     int idx = mTitleIdx;
307                     switch (mIndexerSortMode) {
308                         case ARTIST_MENU:
309                             idx = mArtistIdx;
310                             break;
311                         case ALBUM_MENU:
312                             idx = mAlbumIdx;
313                             break;
314                     }
315                     mIndexer = new MusicAlphabetIndexer(
316                             cursor, idx, getResources().getString(R.string.fast_scroll_alphabet));
317 
318                     // If we have a valid indexer, but the cursor has changed since
319                     // its last use, then point it to the current cursor.
320                 } else {
321                     mIndexer.setCursor(cursor);
322                 }
323             }
324 
325             // Ensure that the list is shown (and initial progress indicator
326             // hidden) in case this is the first cursor we have gotten.
327             makeListShown();
328         }
329 
330         /**
331          * This method is called from a background thread by the list view
332          * when the user has typed a letter that should result in a filtering
333          * of the displayed items.  It returns a Cursor, when will then be
334          * handed to changeCursor.
335          */
336         @Override
runQueryOnBackgroundThread(CharSequence constraint)337         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
338             if (DBG) Log.v(TAG, "Getting new cursor...");
339             return doQuery(true, constraint.toString());
340         }
341 
getPositionForSection(int section)342         public int getPositionForSection(int section) {
343             Cursor cursor = getCursor();
344             if (cursor == null) {
345                 // No cursor, the section doesn't exist so just return 0
346                 return 0;
347             }
348 
349             return mIndexer.getPositionForSection(section);
350         }
351 
getSectionForPosition(int position)352         public int getSectionForPosition(int position) {
353             return 0;
354         }
355 
getSections()356         public Object[] getSections() {
357             if (mIndexer != null) {
358                 return mIndexer.getSections();
359             }
360             return null;
361         }
362     }
363 
364     /**
365      * This is our specialization of AsyncQueryHandler applies new cursors
366      * to our state as they become available.
367      */
368     private final class QueryHandler extends AsyncQueryHandler {
QueryHandler(Context context)369         public QueryHandler(Context context) {
370             super(context.getContentResolver());
371         }
372 
373         @Override
onQueryComplete(int token, Object cookie, Cursor cursor)374         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
375             if (!isFinishing()) {
376                 // Update the adapter: we are no longer loading, and have
377                 // a new cursor for it.
378                 mAdapter.setLoading(false);
379                 mAdapter.changeCursor(cursor);
380                 setProgressBarIndeterminateVisibility(false);
381 
382                 // Now that the cursor is populated again, it's possible to restore the list state
383                 if (mListState != null) {
384                     getListView().onRestoreInstanceState(mListState);
385                     if (mListHasFocus) {
386                         getListView().requestFocus();
387                     }
388                     mListHasFocus = false;
389                     mListState = null;
390                 }
391             } else {
392                 cursor.close();
393             }
394         }
395     }
396 
397     /** Called when the activity is first created. */
398     @Override
onCreate(Bundle icicle)399     public void onCreate(Bundle icicle) {
400         super.onCreate(icicle);
401 
402         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
403 
404         int sortMode = TRACK_MENU;
405         if (icicle == null) {
406             mSelectedUri =
407                     getIntent().getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
408         } else {
409             mSelectedUri = (Uri) icicle.getParcelable(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
410             // Retrieve list state. This will be applied after the
411             // QueryHandler has run
412             mListState = icicle.getParcelable(LIST_STATE_KEY);
413             mListHasFocus = icicle.getBoolean(FOCUS_KEY);
414             sortMode = icicle.getInt(SORT_MODE_KEY, sortMode);
415         }
416         if (Intent.ACTION_GET_CONTENT.equals(getIntent().getAction())) {
417             mBaseUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
418         } else {
419             mBaseUri = getIntent().getData();
420             if (mBaseUri == null) {
421                 Log.w("MusicPicker", "No data URI given to PICK action");
422                 finish();
423                 return;
424             }
425         }
426 
427         setContentView(R.layout.music_picker);
428 
429         mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
430 
431         final ListView listView = getListView();
432 
433         listView.setItemsCanFocus(false);
434 
435         mAdapter = new TrackListAdapter(
436                 this, listView, R.layout.music_picker_item, new String[] {}, new int[] {});
437 
438         setListAdapter(mAdapter);
439 
440         listView.setTextFilterEnabled(true);
441 
442         // We manually save/restore the listview state
443         listView.setSaveEnabled(false);
444 
445         mQueryHandler = new QueryHandler(this);
446 
447         mProgressContainer = findViewById(R.id.progressContainer);
448         mListContainer = findViewById(R.id.listContainer);
449 
450         mOkayButton = findViewById(R.id.okayButton);
451         mOkayButton.setOnClickListener(this);
452         mCancelButton = findViewById(R.id.cancelButton);
453         mCancelButton.setOnClickListener(this);
454 
455         // If there is a currently selected Uri, then try to determine who
456         // it is.
457         if (mSelectedUri != null) {
458             Uri.Builder builder = mSelectedUri.buildUpon();
459             String path = mSelectedUri.getEncodedPath();
460             int idx = path.lastIndexOf('/');
461             if (idx >= 0) {
462                 path = path.substring(0, idx);
463             }
464             builder.encodedPath(path);
465             Uri baseSelectedUri = builder.build();
466             if (DBG) Log.v(TAG, "Selected Uri: " + mSelectedUri);
467             if (DBG) Log.v(TAG, "Selected base Uri: " + baseSelectedUri);
468             if (DBG) Log.v(TAG, "Base Uri: " + mBaseUri);
469             if (baseSelectedUri.equals(mBaseUri)) {
470                 // If the base Uri of the selected Uri is the same as our
471                 // content's base Uri, then use the selection!
472                 mSelectedId = ContentUris.parseId(mSelectedUri);
473             }
474         }
475 
476         setSortMode(sortMode);
477     }
478 
479     @Override
onRestart()480     public void onRestart() {
481         super.onRestart();
482         doQuery(false, null);
483     }
484 
485     @Override
onOptionsItemSelected(MenuItem item)486     public boolean onOptionsItemSelected(MenuItem item) {
487         if (setSortMode(item.getItemId())) {
488             return true;
489         }
490         return super.onOptionsItemSelected(item);
491     }
492 
493     @Override
onCreateOptionsMenu(Menu menu)494     public boolean onCreateOptionsMenu(Menu menu) {
495         super.onCreateOptionsMenu(menu);
496         menu.add(Menu.NONE, TRACK_MENU, Menu.NONE, R.string.sort_by_track);
497         menu.add(Menu.NONE, ALBUM_MENU, Menu.NONE, R.string.sort_by_album);
498         menu.add(Menu.NONE, ARTIST_MENU, Menu.NONE, R.string.sort_by_artist);
499         return true;
500     }
501 
502     @Override
onSaveInstanceState(Bundle icicle)503     protected void onSaveInstanceState(Bundle icicle) {
504         super.onSaveInstanceState(icicle);
505         // Save list state in the bundle so we can restore it after the
506         // QueryHandler has run
507         icicle.putParcelable(LIST_STATE_KEY, getListView().onSaveInstanceState());
508         icicle.putBoolean(FOCUS_KEY, getListView().hasFocus());
509         icicle.putInt(SORT_MODE_KEY, mSortMode);
510     }
511 
512     @Override
onPause()513     public void onPause() {
514         super.onPause();
515         stopMediaPlayer();
516     }
517 
518     @Override
onStop()519     public void onStop() {
520         super.onStop();
521 
522         // We don't want the list to display the empty state, since when we
523         // resume it will still be there and show up while the new query is
524         // happening. After the async query finishes in response to onResume()
525         // setLoading(false) will be called.
526         mAdapter.setLoading(true);
527         mAdapter.changeCursor(null);
528     }
529 
530     /**
531      * Changes the current sort order, building the appropriate query string
532      * for the selected order.
533      */
setSortMode(int sortMode)534     boolean setSortMode(int sortMode) {
535         if (sortMode != mSortMode) {
536             switch (sortMode) {
537                 case TRACK_MENU:
538                     mSortMode = sortMode;
539                     mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
540                     doQuery(false, null);
541                     return true;
542                 case ALBUM_MENU:
543                     mSortMode = sortMode;
544                     mSortOrder = MediaStore.Audio.Media.ALBUM_KEY + " ASC, "
545                             + MediaStore.Audio.Media.TRACK + " ASC, "
546                             + MediaStore.Audio.Media.TITLE_KEY + " ASC";
547                     doQuery(false, null);
548                     return true;
549                 case ARTIST_MENU:
550                     mSortMode = sortMode;
551                     mSortOrder = MediaStore.Audio.Media.ARTIST_KEY + " ASC, "
552                             + MediaStore.Audio.Media.ALBUM_KEY + " ASC, "
553                             + MediaStore.Audio.Media.TRACK + " ASC, "
554                             + MediaStore.Audio.Media.TITLE_KEY + " ASC";
555                     doQuery(false, null);
556                     return true;
557             }
558         }
559         return false;
560     }
561 
562     /**
563      * The first time this is called, we hide the large progress indicator
564      * and show the list view, doing fade animations between them.
565      */
makeListShown()566     void makeListShown() {
567         if (!mListShown) {
568             mListShown = true;
569             mProgressContainer.startAnimation(
570                     AnimationUtils.loadAnimation(this, android.R.anim.fade_out));
571             mProgressContainer.setVisibility(View.GONE);
572             mListContainer.startAnimation(
573                     AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
574             mListContainer.setVisibility(View.VISIBLE);
575         }
576     }
577 
578     /**
579      * Common method for performing a query of the music database, called for
580      * both top-level queries and filtering.
581      *
582      * @param sync If true, this query should be done synchronously and the
583      * resulting cursor returned.  If false, it will be done asynchronously and
584      * null returned.
585      * @param filterstring If non-null, this is a filter to apply to the query.
586      */
doQuery(boolean sync, String filterstring)587     Cursor doQuery(boolean sync, String filterstring) {
588         // Cancel any pending queries
589         mQueryHandler.cancelOperation(MY_QUERY_TOKEN);
590 
591         StringBuilder where = new StringBuilder();
592         where.append(MediaStore.Audio.Media.TITLE + " != ''");
593 
594         // We want to show all audio files, even recordings.  Enforcing the
595         // following condition would hide recordings.
596         // where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
597 
598         Uri uri = mBaseUri;
599         if (!TextUtils.isEmpty(filterstring)) {
600             uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filterstring)).build();
601         }
602 
603         if (sync) {
604             try {
605                 return getContentResolver().query(
606                         uri, CURSOR_COLS, where.toString(), null, mSortOrder);
607             } catch (UnsupportedOperationException ex) {
608             }
609         } else {
610             mAdapter.setLoading(true);
611             setProgressBarIndeterminateVisibility(true);
612             mQueryHandler.startQuery(
613                     MY_QUERY_TOKEN, null, uri, CURSOR_COLS, where.toString(), null, mSortOrder);
614         }
615         return null;
616     }
617 
618     @Override
onListItemClick(ListView l, View v, int position, long id)619     protected void onListItemClick(ListView l, View v, int position, long id) {
620         mCursor.moveToPosition(position);
621         if (DBG)
622             Log.v(TAG,
623                     "Click on " + position + " (id=" + id + ", cursid="
624                             + mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID))
625                             + ") in cursor " + mCursor + " adapter=" + l.getAdapter());
626         setSelected(mCursor);
627     }
628 
setSelected(Cursor c)629     void setSelected(Cursor c) {
630         Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
631         long newId = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID));
632         mSelectedUri = ContentUris.withAppendedId(uri, newId);
633 
634         mSelectedId = newId;
635         if (newId != mPlayingId || mMediaPlayer == null) {
636             stopMediaPlayer();
637             mMediaPlayer = new MediaPlayer();
638             try {
639                 mMediaPlayer.setDataSource(this, mSelectedUri);
640                 mMediaPlayer.setOnCompletionListener(this);
641                 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
642                 mMediaPlayer.prepare();
643                 mMediaPlayer.start();
644                 mPlayingId = newId;
645                 getListView().invalidateViews();
646             } catch (IOException e) {
647                 Log.w("MusicPicker", "Unable to play track", e);
648             }
649         } else if (mMediaPlayer != null) {
650             stopMediaPlayer();
651             getListView().invalidateViews();
652         }
653     }
654 
onCompletion(MediaPlayer mp)655     public void onCompletion(MediaPlayer mp) {
656         if (mMediaPlayer == mp) {
657             mp.stop();
658             mp.release();
659             mMediaPlayer = null;
660             mPlayingId = -1;
661             getListView().invalidateViews();
662         }
663     }
664 
stopMediaPlayer()665     void stopMediaPlayer() {
666         if (mMediaPlayer != null) {
667             mMediaPlayer.stop();
668             mMediaPlayer.release();
669             mMediaPlayer = null;
670             mPlayingId = -1;
671         }
672     }
673 
onClick(View v)674     public void onClick(View v) {
675         switch (v.getId()) {
676             case R.id.okayButton:
677                 if (mSelectedId >= 0) {
678                     setResult(RESULT_OK, new Intent().setData(mSelectedUri));
679                     finish();
680                 }
681                 break;
682 
683             case R.id.cancelButton:
684                 finish();
685                 break;
686         }
687     }
688 }