| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.music; |
| |
| import android.app.ListActivity; |
| import android.content.AsyncQueryHandler; |
| import android.content.ContentUris; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.database.CharArrayBuffer; |
| import android.database.Cursor; |
| import android.media.AudioManager; |
| import android.media.MediaPlayer; |
| import android.media.RingtoneManager; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Parcelable; |
| import android.provider.MediaStore; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.view.animation.AnimationUtils; |
| import android.widget.ImageView; |
| import android.widget.ListView; |
| import android.widget.RadioButton; |
| import android.widget.SectionIndexer; |
| import android.widget.SimpleCursorAdapter; |
| import android.widget.TextView; |
| |
| import java.io.IOException; |
| import java.text.Collator; |
| import java.util.Formatter; |
| import java.util.Locale; |
| |
| /** |
| * Activity allowing the user to select a music track on the device, and |
| * return it to its caller. The music picker user interface is fairly |
| * extensive, providing information about each track like the music |
| * application (title, author, album, duration), as well as the ability to |
| * previous tracks and sort them in different orders. |
| * |
| * <p>This class also illustrates how you can load data from a content |
| * provider asynchronously, providing a good UI while doing so, perform |
| * indexing of the content for use inside of a {@link FastScrollView}, and |
| * perform filtering of the data as the user presses keys. |
| */ |
| public class MusicPicker extends ListActivity |
| implements View.OnClickListener, MediaPlayer.OnCompletionListener, |
| MusicUtils.Defs { |
| static final boolean DBG = false; |
| static final String TAG = "MusicPicker"; |
| |
| /** Holds the previous state of the list, to restore after the async |
| * query has completed. */ |
| static final String LIST_STATE_KEY = "liststate"; |
| /** Remember whether the list last had focus for restoring its state. */ |
| static final String FOCUS_KEY = "focused"; |
| /** Remember the last ordering mode for restoring state. */ |
| static final String SORT_MODE_KEY = "sortMode"; |
| |
| /** Arbitrary number, doesn't matter since we only do one query type. */ |
| static final int MY_QUERY_TOKEN = 42; |
| |
| /** Menu item to sort the music list by track title. */ |
| static final int TRACK_MENU = Menu.FIRST; |
| /** Menu item to sort the music list by album title. */ |
| static final int ALBUM_MENU = Menu.FIRST+1; |
| /** Menu item to sort the music list by artist name. */ |
| static final int ARTIST_MENU = Menu.FIRST+2; |
| |
| /** These are the columns in the music cursor that we are interested in. */ |
| static final String[] CURSOR_COLS = new String[] { |
| MediaStore.Audio.Media._ID, |
| MediaStore.Audio.Media.TITLE, |
| MediaStore.Audio.Media.TITLE_KEY, |
| MediaStore.Audio.Media.DATA, |
| MediaStore.Audio.Media.ALBUM, |
| MediaStore.Audio.Media.ARTIST, |
| MediaStore.Audio.Media.ARTIST_ID, |
| MediaStore.Audio.Media.DURATION, |
| MediaStore.Audio.Media.TRACK |
| }; |
| |
| /** Formatting optimization to avoid creating many temporary objects. */ |
| static StringBuilder sFormatBuilder = new StringBuilder(); |
| /** Formatting optimization to avoid creating many temporary objects. */ |
| static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault()); |
| /** Formatting optimization to avoid creating many temporary objects. */ |
| static final Object[] sTimeArgs = new Object[5]; |
| |
| /** Uri to the directory of all music being displayed. */ |
| Uri mBaseUri; |
| |
| /** This is the adapter used to display all of the tracks. */ |
| TrackListAdapter mAdapter; |
| /** Our instance of QueryHandler used to perform async background queries. */ |
| QueryHandler mQueryHandler; |
| |
| /** Used to keep track of the last scroll state of the list. */ |
| Parcelable mListState = null; |
| /** Used to keep track of whether the list last had focus. */ |
| boolean mListHasFocus; |
| |
| /** The current cursor on the music that is being displayed. */ |
| Cursor mCursor; |
| /** The actual sort order the user has selected. */ |
| int mSortMode = -1; |
| /** SQL order by string describing the currently selected sort order. */ |
| String mSortOrder; |
| |
| /** Container of the in-screen progress indicator, to be able to hide it |
| * when done loading the initial cursor. */ |
| View mProgressContainer; |
| /** Container of the list view hierarchy, to be able to show it when done |
| * loading the initial cursor. */ |
| View mListContainer; |
| /** Set to true when the list view has been shown for the first time. */ |
| boolean mListShown; |
| |
| /** View holding the okay button. */ |
| View mOkayButton; |
| /** View holding the cancel button. */ |
| View mCancelButton; |
| |
| /** Which track row ID the user has last selected. */ |
| long mSelectedId = -1; |
| /** Completel Uri that the user has last selected. */ |
| Uri mSelectedUri; |
| |
| /** If >= 0, we are currently playing a track for preview, and this is its |
| * row ID. */ |
| long mPlayingId = -1; |
| |
| /** This is used for playing previews of the music files. */ |
| MediaPlayer mMediaPlayer; |
| |
| /** |
| * A special implementation of SimpleCursorAdapter that knows how to bind |
| * our cursor data to our list item structure, and takes care of other |
| * advanced features such as indexing and filtering. |
| */ |
| class TrackListAdapter extends SimpleCursorAdapter |
| implements SectionIndexer { |
| final ListView mListView; |
| |
| private final StringBuilder mBuilder = new StringBuilder(); |
| private final String mUnknownArtist; |
| private final String mUnknownAlbum; |
| |
| private int mIdIdx; |
| private int mTitleIdx; |
| private int mArtistIdx; |
| private int mAlbumIdx; |
| private int mDurationIdx; |
| |
| private boolean mLoading = true; |
| private int mIndexerSortMode; |
| private MusicAlphabetIndexer mIndexer; |
| |
| class ViewHolder { |
| TextView line1; |
| TextView line2; |
| TextView duration; |
| RadioButton radio; |
| ImageView play_indicator; |
| CharArrayBuffer buffer1; |
| char [] buffer2; |
| } |
| |
| TrackListAdapter(Context context, ListView listView, int layout, |
| String[] from, int[] to) { |
| super(context, layout, null, from, to); |
| mListView = listView; |
| mUnknownArtist = context.getString(R.string.unknown_artist_name); |
| mUnknownAlbum = context.getString(R.string.unknown_album_name); |
| } |
| |
| /** |
| * The mLoading flag is set while we are performing a background |
| * query, to avoid displaying the "No music" empty view during |
| * this time. |
| */ |
| public void setLoading(boolean loading) { |
| mLoading = loading; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| if (mLoading) { |
| // We don't want the empty state to show when loading. |
| return false; |
| } else { |
| return super.isEmpty(); |
| } |
| } |
| |
| @Override |
| public View newView(Context context, Cursor cursor, ViewGroup parent) { |
| View v = super.newView(context, cursor, parent); |
| ViewHolder vh = new ViewHolder(); |
| vh.line1 = (TextView) v.findViewById(R.id.line1); |
| vh.line2 = (TextView) v.findViewById(R.id.line2); |
| vh.duration = (TextView) v.findViewById(R.id.duration); |
| vh.radio = (RadioButton) v.findViewById(R.id.radio); |
| vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); |
| vh.buffer1 = new CharArrayBuffer(100); |
| vh.buffer2 = new char[200]; |
| v.setTag(vh); |
| return v; |
| } |
| |
| @Override |
| public void bindView(View view, Context context, Cursor cursor) { |
| ViewHolder vh = (ViewHolder) view.getTag(); |
| |
| cursor.copyStringToBuffer(mTitleIdx, vh.buffer1); |
| vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied); |
| |
| int secs = cursor.getInt(mDurationIdx) / 1000; |
| if (secs == 0) { |
| vh.duration.setText(""); |
| } else { |
| vh.duration.setText(MusicUtils.makeTimeString(context, secs)); |
| } |
| |
| final StringBuilder builder = mBuilder; |
| builder.delete(0, builder.length()); |
| |
| String name = cursor.getString(mAlbumIdx); |
| if (name == null || name.equals("<unknown>")) { |
| builder.append(mUnknownAlbum); |
| } else { |
| builder.append(name); |
| } |
| builder.append('\n'); |
| name = cursor.getString(mArtistIdx); |
| if (name == null || name.equals("<unknown>")) { |
| builder.append(mUnknownArtist); |
| } else { |
| builder.append(name); |
| } |
| int len = builder.length(); |
| if (vh.buffer2.length < len) { |
| vh.buffer2 = new char[len]; |
| } |
| builder.getChars(0, len, vh.buffer2, 0); |
| vh.line2.setText(vh.buffer2, 0, len); |
| |
| // Update the checkbox of the item, based on which the user last |
| // selected. Note that doing it this way means we must have the |
| // list view update all of its items when the selected item |
| // changes. |
| final long id = cursor.getLong(mIdIdx); |
| vh.radio.setChecked(id == mSelectedId); |
| if (DBG) Log.v(TAG, "Binding id=" + id + " sel=" + mSelectedId |
| + " playing=" + mPlayingId + " cursor=" + cursor); |
| |
| // Likewise, display the "now playing" icon if this item is |
| // currently being previewed for the user. |
| ImageView iv = vh.play_indicator; |
| if (id == mPlayingId) { |
| iv.setImageResource(R.drawable.indicator_ic_mp_playing_list); |
| iv.setVisibility(View.VISIBLE); |
| } else { |
| iv.setVisibility(View.GONE); |
| } |
| } |
| |
| /** |
| * This method is called whenever we receive a new cursor due to |
| * an async query, and must take care of plugging the new one in |
| * to the adapter. |
| */ |
| @Override |
| public void changeCursor(Cursor cursor) { |
| super.changeCursor(cursor); |
| if (DBG) Log.v(TAG, "Setting cursor to: " + cursor |
| + " from: " + MusicPicker.this.mCursor); |
| |
| MusicPicker.this.mCursor = cursor; |
| |
| if (cursor != null) { |
| // Retrieve indices of the various columns we are interested in. |
| mIdIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID); |
| mTitleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE); |
| mArtistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST); |
| mAlbumIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM); |
| mDurationIdx = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION); |
| |
| // If the sort mode has changed, or we haven't yet created an |
| // indexer one, then create a new one that is indexing the |
| // appropriate column based on the sort mode. |
| if (mIndexerSortMode != mSortMode || mIndexer == null) { |
| mIndexerSortMode = mSortMode; |
| int idx = mTitleIdx; |
| switch (mIndexerSortMode) { |
| case ARTIST_MENU: |
| idx = mArtistIdx; |
| break; |
| case ALBUM_MENU: |
| idx = mAlbumIdx; |
| break; |
| } |
| mIndexer = new MusicAlphabetIndexer(cursor, idx, |
| getResources().getString(R.string.fast_scroll_alphabet)); |
| |
| // If we have a valid indexer, but the cursor has changed since |
| // its last use, then point it to the current cursor. |
| } else { |
| mIndexer.setCursor(cursor); |
| } |
| } |
| |
| // Ensure that the list is shown (and initial progress indicator |
| // hidden) in case this is the first cursor we have gotten. |
| makeListShown(); |
| } |
| |
| /** |
| * This method is called from a background thread by the list view |
| * when the user has typed a letter that should result in a filtering |
| * of the displayed items. It returns a Cursor, when will then be |
| * handed to changeCursor. |
| */ |
| @Override |
| public Cursor runQueryOnBackgroundThread(CharSequence constraint) { |
| if (DBG) Log.v(TAG, "Getting new cursor..."); |
| return doQuery(true, constraint.toString()); |
| } |
| |
| public int getPositionForSection(int section) { |
| Cursor cursor = getCursor(); |
| if (cursor == null) { |
| // No cursor, the section doesn't exist so just return 0 |
| return 0; |
| } |
| |
| return mIndexer.getPositionForSection(section); |
| } |
| |
| public int getSectionForPosition(int position) { |
| return 0; |
| } |
| |
| public Object[] getSections() { |
| if (mIndexer != null) { |
| return mIndexer.getSections(); |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * This is our specialization of AsyncQueryHandler applies new cursors |
| * to our state as they become available. |
| */ |
| private final class QueryHandler extends AsyncQueryHandler { |
| public QueryHandler(Context context) { |
| super(context.getContentResolver()); |
| } |
| |
| @Override |
| protected void onQueryComplete(int token, Object cookie, Cursor cursor) { |
| if (!isFinishing()) { |
| // Update the adapter: we are no longer loading, and have |
| // a new cursor for it. |
| mAdapter.setLoading(false); |
| mAdapter.changeCursor(cursor); |
| setProgressBarIndeterminateVisibility(false); |
| |
| // Now that the cursor is populated again, it's possible to restore the list state |
| if (mListState != null) { |
| getListView().onRestoreInstanceState(mListState); |
| if (mListHasFocus) { |
| getListView().requestFocus(); |
| } |
| mListHasFocus = false; |
| mListState = null; |
| } |
| } else { |
| cursor.close(); |
| } |
| } |
| } |
| |
| /** Called when the activity is first created. */ |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); |
| |
| int sortMode = TRACK_MENU; |
| if (icicle == null) { |
| mSelectedUri = getIntent().getParcelableExtra( |
| RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); |
| } else { |
| mSelectedUri = (Uri)icicle.getParcelable( |
| RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); |
| // Retrieve list state. This will be applied after the |
| // QueryHandler has run |
| mListState = icicle.getParcelable(LIST_STATE_KEY); |
| mListHasFocus = icicle.getBoolean(FOCUS_KEY); |
| sortMode = icicle.getInt(SORT_MODE_KEY, sortMode); |
| } |
| if (Intent.ACTION_GET_CONTENT.equals(getIntent().getAction())) { |
| mBaseUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; |
| } else { |
| mBaseUri = getIntent().getData(); |
| if (mBaseUri == null) { |
| Log.w("MusicPicker", "No data URI given to PICK action"); |
| finish(); |
| return; |
| } |
| } |
| |
| setContentView(R.layout.music_picker); |
| |
| mSortOrder = MediaStore.Audio.Media.TITLE_KEY; |
| |
| final ListView listView = getListView(); |
| |
| listView.setItemsCanFocus(false); |
| |
| mAdapter = new TrackListAdapter(this, listView, |
| R.layout.music_picker_item, new String[] {}, |
| new int[] {}); |
| |
| setListAdapter(mAdapter); |
| |
| listView.setTextFilterEnabled(true); |
| |
| // We manually save/restore the listview state |
| listView.setSaveEnabled(false); |
| |
| mQueryHandler = new QueryHandler(this); |
| |
| mProgressContainer = findViewById(R.id.progressContainer); |
| mListContainer = findViewById(R.id.listContainer); |
| |
| mOkayButton = findViewById(R.id.okayButton); |
| mOkayButton.setOnClickListener(this); |
| mCancelButton = findViewById(R.id.cancelButton); |
| mCancelButton.setOnClickListener(this); |
| |
| // If there is a currently selected Uri, then try to determine who |
| // it is. |
| if (mSelectedUri != null) { |
| Uri.Builder builder = mSelectedUri.buildUpon(); |
| String path = mSelectedUri.getEncodedPath(); |
| int idx = path.lastIndexOf('/'); |
| if (idx >= 0) { |
| path = path.substring(0, idx); |
| } |
| builder.encodedPath(path); |
| Uri baseSelectedUri = builder.build(); |
| if (DBG) Log.v(TAG, "Selected Uri: " + mSelectedUri); |
| if (DBG) Log.v(TAG, "Selected base Uri: " + baseSelectedUri); |
| if (DBG) Log.v(TAG, "Base Uri: " + mBaseUri); |
| if (baseSelectedUri.equals(mBaseUri)) { |
| // If the base Uri of the selected Uri is the same as our |
| // content's base Uri, then use the selection! |
| mSelectedId = ContentUris.parseId(mSelectedUri); |
| } |
| } |
| |
| setSortMode(sortMode); |
| } |
| |
| @Override public void onRestart() { |
| super.onRestart(); |
| doQuery(false, null); |
| } |
| |
| @Override public boolean onOptionsItemSelected(MenuItem item) { |
| if (setSortMode(item.getItemId())) { |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override public boolean onCreateOptionsMenu(Menu menu) { |
| super.onCreateOptionsMenu(menu); |
| menu.add(Menu.NONE, TRACK_MENU, Menu.NONE, R.string.sort_by_track); |
| menu.add(Menu.NONE, ALBUM_MENU, Menu.NONE, R.string.sort_by_album); |
| menu.add(Menu.NONE, ARTIST_MENU, Menu.NONE, R.string.sort_by_artist); |
| return true; |
| } |
| |
| @Override protected void onSaveInstanceState(Bundle icicle) { |
| super.onSaveInstanceState(icicle); |
| // Save list state in the bundle so we can restore it after the |
| // QueryHandler has run |
| icicle.putParcelable(LIST_STATE_KEY, getListView().onSaveInstanceState()); |
| icicle.putBoolean(FOCUS_KEY, getListView().hasFocus()); |
| icicle.putInt(SORT_MODE_KEY, mSortMode); |
| } |
| |
| @Override public void onPause() { |
| super.onPause(); |
| stopMediaPlayer(); |
| } |
| |
| @Override public void onStop() { |
| super.onStop(); |
| |
| // We don't want the list to display the empty state, since when we |
| // resume it will still be there and show up while the new query is |
| // happening. After the async query finishes in response to onResume() |
| // setLoading(false) will be called. |
| mAdapter.setLoading(true); |
| mAdapter.changeCursor(null); |
| } |
| |
| /** |
| * Changes the current sort order, building the appropriate query string |
| * for the selected order. |
| */ |
| boolean setSortMode(int sortMode) { |
| if (sortMode != mSortMode) { |
| switch (sortMode) { |
| case TRACK_MENU: |
| mSortMode = sortMode; |
| mSortOrder = MediaStore.Audio.Media.TITLE_KEY; |
| doQuery(false, null); |
| return true; |
| case ALBUM_MENU: |
| mSortMode = sortMode; |
| mSortOrder = MediaStore.Audio.Media.ALBUM_KEY + " ASC, " |
| + MediaStore.Audio.Media.TRACK + " ASC, " |
| + MediaStore.Audio.Media.TITLE_KEY + " ASC"; |
| doQuery(false, null); |
| return true; |
| case ARTIST_MENU: |
| mSortMode = sortMode; |
| mSortOrder = MediaStore.Audio.Media.ARTIST_KEY + " ASC, " |
| + MediaStore.Audio.Media.ALBUM_KEY + " ASC, " |
| + MediaStore.Audio.Media.TRACK + " ASC, " |
| + MediaStore.Audio.Media.TITLE_KEY + " ASC"; |
| doQuery(false, null); |
| return true; |
| } |
| |
| } |
| return false; |
| } |
| |
| /** |
| * The first time this is called, we hide the large progress indicator |
| * and show the list view, doing fade animations between them. |
| */ |
| void makeListShown() { |
| if (!mListShown) { |
| mListShown = true; |
| mProgressContainer.startAnimation(AnimationUtils.loadAnimation( |
| this, android.R.anim.fade_out)); |
| mProgressContainer.setVisibility(View.GONE); |
| mListContainer.startAnimation(AnimationUtils.loadAnimation( |
| this, android.R.anim.fade_in)); |
| mListContainer.setVisibility(View.VISIBLE); |
| } |
| } |
| |
| /** |
| * Common method for performing a query of the music database, called for |
| * both top-level queries and filtering. |
| * |
| * @param sync If true, this query should be done synchronously and the |
| * resulting cursor returned. If false, it will be done asynchronously and |
| * null returned. |
| * @param filterstring If non-null, this is a filter to apply to the query. |
| */ |
| Cursor doQuery(boolean sync, String filterstring) { |
| // Cancel any pending queries |
| mQueryHandler.cancelOperation(MY_QUERY_TOKEN); |
| |
| StringBuilder where = new StringBuilder(); |
| where.append(MediaStore.Audio.Media.TITLE + " != ''"); |
| |
| // We want to show all audio files, even recordings. Enforcing the |
| // following condition would hide recordings. |
| //where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1"); |
| |
| Uri uri = mBaseUri; |
| if (!TextUtils.isEmpty(filterstring)) { |
| uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filterstring)).build(); |
| } |
| |
| if (sync) { |
| try { |
| return getContentResolver().query(uri, CURSOR_COLS, |
| where.toString(), null, mSortOrder); |
| } catch (UnsupportedOperationException ex) { |
| } |
| } else { |
| mAdapter.setLoading(true); |
| setProgressBarIndeterminateVisibility(true); |
| mQueryHandler.startQuery(MY_QUERY_TOKEN, null, uri, CURSOR_COLS, |
| where.toString(), null, mSortOrder); |
| } |
| return null; |
| } |
| |
| @Override protected void onListItemClick(ListView l, View v, int position, |
| long id) { |
| mCursor.moveToPosition(position); |
| if (DBG) Log.v(TAG, "Click on " + position + " (id=" + id |
| + ", cursid=" |
| + mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID)) |
| + ") in cursor " + mCursor |
| + " adapter=" + l.getAdapter()); |
| setSelected(mCursor); |
| } |
| |
| void setSelected(Cursor c) { |
| Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; |
| long newId = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID)); |
| mSelectedUri = ContentUris.withAppendedId(uri, newId); |
| |
| mSelectedId = newId; |
| if (newId != mPlayingId || mMediaPlayer == null) { |
| stopMediaPlayer(); |
| mMediaPlayer = new MediaPlayer(); |
| try { |
| mMediaPlayer.setDataSource(this, mSelectedUri); |
| mMediaPlayer.setOnCompletionListener(this); |
| mMediaPlayer.setAudioStreamType(AudioManager.STREAM_RING); |
| mMediaPlayer.prepare(); |
| mMediaPlayer.start(); |
| mPlayingId = newId; |
| getListView().invalidateViews(); |
| } catch (IOException e) { |
| Log.w("MusicPicker", "Unable to play track", e); |
| } |
| } else if (mMediaPlayer != null) { |
| stopMediaPlayer(); |
| getListView().invalidateViews(); |
| } |
| } |
| |
| public void onCompletion(MediaPlayer mp) { |
| if (mMediaPlayer == mp) { |
| mp.stop(); |
| mp.release(); |
| mMediaPlayer = null; |
| mPlayingId = -1; |
| getListView().invalidateViews(); |
| } |
| } |
| |
| void stopMediaPlayer() { |
| if (mMediaPlayer != null) { |
| mMediaPlayer.stop(); |
| mMediaPlayer.release(); |
| mMediaPlayer = null; |
| mPlayingId = -1; |
| } |
| } |
| |
| public void onClick(View v) { |
| switch (v.getId()) { |
| case R.id.okayButton: |
| if (mSelectedId >= 0) { |
| setResult(RESULT_OK, new Intent().setData(mSelectedUri)); |
| finish(); |
| } |
| break; |
| |
| case R.id.cancelButton: |
| finish(); |
| break; |
| } |
| } |
| } |