MusicPickerpublic class MusicPicker extends android.app.ListActivity implements MediaPlayer.OnCompletionListener, View.OnClickListener, MusicUtils.DefsActivity 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.
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. |
Fields Summary |
---|
static final boolean | DBG | static final String | TAG | static final String | LIST_STATE_KEYHolds the previous state of the list, to restore after the async
query has completed. | static final String | FOCUS_KEYRemember whether the list last had focus for restoring its state. | static final String | SORT_MODE_KEYRemember the last ordering mode for restoring state. | final int | MY_QUERY_TOKENArbitrary number, doesn't matter since we only do one query type. | static final int | TRACK_MENUMenu item to sort the music list by track title. | static final int | ALBUM_MENUMenu item to sort the music list by album title. | static final int | ARTIST_MENUMenu item to sort the music list by artist name. | static final String[] | CURSOR_COLSThese are the columns in the music cursor that we are interested in. | static StringBuilder | sFormatBuilderFormatting optimization to avoid creating many temporary objects. | static Formatter | sFormatterFormatting optimization to avoid creating many temporary objects. | static final Object[] | sTimeArgsFormatting optimization to avoid creating many temporary objects. | android.net.Uri | mBaseUriUri to the directory of all music being displayed. | TrackListAdapter | mAdapterThis is the adapter used to display all of the tracks. | QueryHandler | mQueryHandlerOur instance of QueryHandler used to perform async background queries. | android.os.Parcelable | mListStateUsed to keep track of the last scroll state of the list. | boolean | mListHasFocusUsed to keep track of whether the list last had focus. | android.database.Cursor | mCursorThe current cursor on the music that is being displayed. | int | mSortModeThe actual sort order the user has selected. | String | mSortOrderSQL order by string describing the currently selected sort order. | android.view.View | mProgressContainerContainer of the in-screen progress indicator, to be able to hide it
when done loading the initial cursor. | android.view.View | mListContainerContainer of the list view hierarchy, to be able to show it when done
loading the initial cursor. | boolean | mListShownSet to true when the list view has been shown for the first time. | android.view.View | mOkayButtonView holding the okay button. | android.view.View | mCancelButtonView holding the cancel button. | long | mSelectedIdWhich track row ID the user has last selected. | android.net.Uri | mSelectedUriCompletel Uri that the user has last selected. | long | mPlayingIdIf >= 0, we are currently playing a track for preview, and this is its
row ID. | android.media.MediaPlayer | mMediaPlayerThis is used for playing previews of the music files. |
Methods Summary |
---|
android.database.Cursor | doQuery(boolean sync, java.lang.String filterstring)Common method for performing a query of the music database, called for
both top-level queries and filtering.
// Cancel any pending queries
mQueryHandler.cancelOperation(MY_QUERY_TOKEN);
StringBuilder where = new StringBuilder();
where.append(MediaStore.Audio.Media.TITLE + " != ''");
// Add in the filtering constraints
String [] keywords = null;
if (filterstring != null) {
String [] searchWords = filterstring.split(" ");
keywords = new String[searchWords.length];
Collator col = Collator.getInstance();
col.setStrength(Collator.PRIMARY);
for (int i = 0; i < searchWords.length; i++) {
keywords[i] = '%" + MediaStore.Audio.keyFor(searchWords[i]) + '%";
}
for (int i = 0; i < searchWords.length; i++) {
where.append(" AND ");
where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
where.append(MediaStore.Audio.Media.ALBUM_KEY + "||");
where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?");
}
}
// 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");
if (sync) {
try {
return getContentResolver().query(mBaseUri, CURSOR_COLS,
where.toString(), keywords, mSortOrder);
} catch (UnsupportedOperationException ex) {
}
} else {
mAdapter.setLoading(true);
setProgressBarIndeterminateVisibility(true);
mQueryHandler.startQuery(MY_QUERY_TOKEN, null, mBaseUri, CURSOR_COLS,
where.toString(), keywords, mSortOrder);
}
return null;
| void | makeListShown()The first time this is called, we hide the large progress indicator
and show the list view, doing fade animations between them.
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);
}
| public static java.lang.String | makeTimeString(android.content.Context context, long secs)
String durationformat = context.getString(R.string.durationformat);
/* Provide multiple arguments so the format can be changed easily
* by modifying the xml.
*/
sFormatBuilder.setLength(0);
final Object[] timeArgs = sTimeArgs;
timeArgs[0] = secs / 3600;
timeArgs[1] = secs / 60;
timeArgs[2] = (secs / 60) % 60;
timeArgs[3] = secs;
timeArgs[4] = secs % 60;
return sFormatter.format(durationformat, timeArgs).toString();
| public void | onClick(android.view.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;
}
| public void | onCompletion(android.media.MediaPlayer mp)
if (mMediaPlayer == mp) {
mp.stop();
mp.release();
mMediaPlayer = null;
mPlayingId = -1;
getListView().invalidateViews();
}
| public void | onCreate(android.os.Bundle icicle)Called when the activity is first created.
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);
| public boolean | onCreateOptionsMenu(android.view.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;
| protected void | onListItemClick(android.widget.ListView l, android.view.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);
| public boolean | onOptionsItemSelected(android.view.MenuItem item)
if (setSortMode(item.getItemId())) {
return true;
}
return super.onOptionsItemSelected(item);
| public void | onPause()
super.onPause();
stopMediaPlayer();
| public void | onRestart()
super.onRestart();
doQuery(false, null);
| protected void | onSaveInstanceState(android.os.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);
| 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);
| void | setSelected(android.database.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();
}
| boolean | setSortMode(int sortMode)Changes the current sort order, building the appropriate query string
for the selected order.
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;
| void | stopMediaPlayer()
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
mPlayingId = -1;
}
|
|