• 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 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 
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, "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
changeCursor(Cursor cursor)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
runQueryOnBackgroundThread(CharSequence constraint)336         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
337             if (DBG) Log.v(TAG, "Getting new cursor...");
338             return doQuery(true, constraint.toString());
339         }
340 
getPositionForSection(int section)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 
getSectionForPosition(int position)351         public int getSectionForPosition(int position) {
352             return 0;
353         }
354 
getSections()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 {
QueryHandler(Context context)368         public QueryHandler(Context context) {
369             super(context.getContentResolver());
370         }
371 
372         @Override
onQueryComplete(int token, Object cookie, Cursor cursor)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
onCreate(Bundle icicle)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
onRestart()479     public void onRestart() {
480         super.onRestart();
481         doQuery(false, null);
482     }
483 
484     @Override
onOptionsItemSelected(MenuItem item)485     public boolean onOptionsItemSelected(MenuItem item) {
486         if (setSortMode(item.getItemId())) {
487             return true;
488         }
489         return super.onOptionsItemSelected(item);
490     }
491 
492     @Override
onCreateOptionsMenu(Menu menu)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
onSaveInstanceState(Bundle icicle)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
onPause()512     public void onPause() {
513         super.onPause();
514         stopMediaPlayer();
515     }
516 
517     @Override
onStop()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      */
setSortMode(int sortMode)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      */
makeListShown()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      */
doQuery(boolean sync, String filterstring)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
onListItemClick(ListView l, View v, int position, long id)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 
setSelected(Cursor c)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 
onCompletion(MediaPlayer mp)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 
stopMediaPlayer()663     void stopMediaPlayer() {
664         if (mMediaPlayer != null) {
665             mMediaPlayer.stop();
666             mMediaPlayer.release();
667             mMediaPlayer = null;
668             mPlayingId = -1;
669         }
670     }
671 
onClick(View v)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