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 }