FileDocCategorySizeDatePackage
MusicPicker.javaAPI DocAndroid 1.5 API28447Wed May 06 22:42:46 BST 2009com.android.music

MusicPicker

public class MusicPicker extends android.app.ListActivity implements MediaPlayer.OnCompletionListener, View.OnClickListener, MusicUtils.Defs
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.

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_KEY
Holds the previous state of the list, to restore after the async query has completed.
static final String
FOCUS_KEY
Remember whether the list last had focus for restoring its state.
static final String
SORT_MODE_KEY
Remember the last ordering mode for restoring state.
final int
MY_QUERY_TOKEN
Arbitrary number, doesn't matter since we only do one query type.
static final int
TRACK_MENU
Menu item to sort the music list by track title.
static final int
ALBUM_MENU
Menu item to sort the music list by album title.
static final int
ARTIST_MENU
Menu item to sort the music list by artist name.
static final String[]
CURSOR_COLS
These are the columns in the music cursor that we are interested in.
static StringBuilder
sFormatBuilder
Formatting optimization to avoid creating many temporary objects.
static Formatter
sFormatter
Formatting optimization to avoid creating many temporary objects.
static final Object[]
sTimeArgs
Formatting optimization to avoid creating many temporary objects.
android.net.Uri
mBaseUri
Uri to the directory of all music being displayed.
TrackListAdapter
mAdapter
This is the adapter used to display all of the tracks.
QueryHandler
mQueryHandler
Our instance of QueryHandler used to perform async background queries.
android.os.Parcelable
mListState
Used to keep track of the last scroll state of the list.
boolean
mListHasFocus
Used to keep track of whether the list last had focus.
android.database.Cursor
mCursor
The current cursor on the music that is being displayed.
int
mSortMode
The actual sort order the user has selected.
String
mSortOrder
SQL order by string describing the currently selected sort order.
android.view.View
mProgressContainer
Container of the in-screen progress indicator, to be able to hide it when done loading the initial cursor.
android.view.View
mListContainer
Container of the list view hierarchy, to be able to show it when done loading the initial cursor.
boolean
mListShown
Set to true when the list view has been shown for the first time.
android.view.View
mOkayButton
View holding the okay button.
android.view.View
mCancelButton
View holding the cancel button.
long
mSelectedId
Which track row ID the user has last selected.
android.net.Uri
mSelectedUri
Completel Uri that the user has last selected.
long
mPlayingId
If >= 0, we are currently playing a track for preview, and this is its row ID.
android.media.MediaPlayer
mMediaPlayer
This is used for playing previews of the music files.
Constructors Summary
Methods Summary
android.database.CursordoQuery(boolean sync, java.lang.String filterstring)
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.

        // 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;
    
voidmakeListShown()
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.StringmakeTimeString(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 voidonClick(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 voidonCompletion(android.media.MediaPlayer mp)

        if (mMediaPlayer == mp) {
            mp.stop();
            mp.release();
            mMediaPlayer = null;
            mPlayingId = -1;
            getListView().invalidateViews();
        }
    
public voidonCreate(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 booleanonCreateOptionsMenu(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 voidonListItemClick(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 booleanonOptionsItemSelected(android.view.MenuItem item)

        if (setSortMode(item.getItemId())) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    
public voidonPause()

        super.onPause();
        stopMediaPlayer();
    
public voidonRestart()

        super.onRestart();
        doQuery(false, null);
    
protected voidonSaveInstanceState(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 voidonStop()

        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);
    
voidsetSelected(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();
        }
    
booleansetSortMode(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;
    
voidstopMediaPlayer()

        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            mPlayingId = -1;
        }