| /* |
| * Copyright (C) 2007 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 com.android.music.MusicUtils.ServiceToken; |
| |
| import android.app.ListActivity; |
| import android.app.SearchManager; |
| import android.content.AsyncQueryHandler; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.ServiceConnection; |
| import android.database.AbstractCursor; |
| import android.database.CharArrayBuffer; |
| import android.database.Cursor; |
| import android.graphics.Bitmap; |
| import android.media.AudioManager; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.provider.MediaStore; |
| import android.provider.MediaStore.Audio.Playlists; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.ContextMenu; |
| import android.view.KeyEvent; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.SubMenu; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.view.ContextMenu.ContextMenuInfo; |
| import android.widget.AlphabetIndexer; |
| import android.widget.ImageView; |
| import android.widget.ListView; |
| import android.widget.SectionIndexer; |
| import android.widget.SimpleCursorAdapter; |
| import android.widget.TextView; |
| import android.widget.AdapterView.AdapterContextMenuInfo; |
| |
| import java.text.Collator; |
| import java.util.Arrays; |
| |
| public class TrackBrowserActivity extends ListActivity |
| implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection |
| { |
| private static final int Q_SELECTED = CHILD_MENU_BASE; |
| private static final int Q_ALL = CHILD_MENU_BASE + 1; |
| private static final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2; |
| private static final int PLAY_ALL = CHILD_MENU_BASE + 3; |
| private static final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4; |
| private static final int REMOVE = CHILD_MENU_BASE + 5; |
| private static final int SEARCH = CHILD_MENU_BASE + 6; |
| |
| |
| private static final String LOGTAG = "TrackBrowser"; |
| |
| private String[] mCursorCols; |
| private String[] mPlaylistMemberCols; |
| private boolean mDeletedOneRow = false; |
| private boolean mEditMode = false; |
| private String mCurrentTrackName; |
| private String mCurrentAlbumName; |
| private String mCurrentArtistNameForAlbum; |
| private ListView mTrackList; |
| private Cursor mTrackCursor; |
| private TrackListAdapter mAdapter; |
| private boolean mAdapterSent = false; |
| private String mAlbumId; |
| private String mArtistId; |
| private String mPlaylist; |
| private String mGenre; |
| private String mSortOrder; |
| private int mSelectedPosition; |
| private long mSelectedId; |
| private static int mLastListPosCourse = -1; |
| private static int mLastListPosFine = -1; |
| private boolean mUseLastListPos = false; |
| private ServiceToken mToken; |
| |
| public TrackBrowserActivity() |
| { |
| } |
| |
| /** Called when the activity is first created. */ |
| @Override |
| public void onCreate(Bundle icicle) |
| { |
| super.onCreate(icicle); |
| requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); |
| Intent intent = getIntent(); |
| if (intent != null) { |
| if (intent.getBooleanExtra("withtabs", false)) { |
| requestWindowFeature(Window.FEATURE_NO_TITLE); |
| } |
| } |
| setVolumeControlStream(AudioManager.STREAM_MUSIC); |
| if (icicle != null) { |
| mSelectedId = icicle.getLong("selectedtrack"); |
| mAlbumId = icicle.getString("album"); |
| mArtistId = icicle.getString("artist"); |
| mPlaylist = icicle.getString("playlist"); |
| mGenre = icicle.getString("genre"); |
| mEditMode = icicle.getBoolean("editmode", false); |
| } else { |
| mAlbumId = intent.getStringExtra("album"); |
| // If we have an album, show everything on the album, not just stuff |
| // by a particular artist. |
| mArtistId = intent.getStringExtra("artist"); |
| mPlaylist = intent.getStringExtra("playlist"); |
| mGenre = intent.getStringExtra("genre"); |
| mEditMode = intent.getAction().equals(Intent.ACTION_EDIT); |
| } |
| |
| mCursorCols = new String[] { |
| MediaStore.Audio.Media._ID, |
| MediaStore.Audio.Media.TITLE, |
| MediaStore.Audio.Media.DATA, |
| MediaStore.Audio.Media.ALBUM, |
| MediaStore.Audio.Media.ARTIST, |
| MediaStore.Audio.Media.ARTIST_ID, |
| MediaStore.Audio.Media.DURATION |
| }; |
| mPlaylistMemberCols = new String[] { |
| MediaStore.Audio.Playlists.Members._ID, |
| MediaStore.Audio.Media.TITLE, |
| MediaStore.Audio.Media.DATA, |
| MediaStore.Audio.Media.ALBUM, |
| MediaStore.Audio.Media.ARTIST, |
| MediaStore.Audio.Media.ARTIST_ID, |
| MediaStore.Audio.Media.DURATION, |
| MediaStore.Audio.Playlists.Members.PLAY_ORDER, |
| MediaStore.Audio.Playlists.Members.AUDIO_ID, |
| MediaStore.Audio.Media.IS_MUSIC |
| }; |
| |
| setContentView(R.layout.media_picker_activity); |
| mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab); |
| mTrackList = getListView(); |
| mTrackList.setOnCreateContextMenuListener(this); |
| mTrackList.setCacheColorHint(0); |
| if (mEditMode) { |
| ((TouchInterceptor) mTrackList).setDropListener(mDropListener); |
| ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener); |
| mTrackList.setDivider(null); |
| mTrackList.setSelector(R.drawable.list_selector_background); |
| } else { |
| mTrackList.setTextFilterEnabled(true); |
| } |
| mAdapter = (TrackListAdapter) getLastNonConfigurationInstance(); |
| |
| if (mAdapter != null) { |
| mAdapter.setActivity(this); |
| setListAdapter(mAdapter); |
| } |
| mToken = MusicUtils.bindToService(this, this); |
| |
| // don't set the album art until after the view has been layed out |
| mTrackList.post(new Runnable() { |
| |
| public void run() { |
| setAlbumArtBackground(); |
| } |
| }); |
| } |
| |
| public void onServiceConnected(ComponentName name, IBinder service) |
| { |
| IntentFilter f = new IntentFilter(); |
| f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); |
| f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); |
| f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); |
| f.addDataScheme("file"); |
| registerReceiver(mScanListener, f); |
| |
| if (mAdapter == null) { |
| //Log.i("@@@", "starting query"); |
| mAdapter = new TrackListAdapter( |
| getApplication(), // need to use application context to avoid leaks |
| this, |
| mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item, |
| null, // cursor |
| new String[] {}, |
| new int[] {}, |
| "nowplaying".equals(mPlaylist), |
| mPlaylist != null && |
| !(mPlaylist.equals("podcasts") || mPlaylist.equals("recentlyadded"))); |
| setListAdapter(mAdapter); |
| setTitle(R.string.working_songs); |
| getTrackCursor(mAdapter.getQueryHandler(), null, true); |
| } else { |
| mTrackCursor = mAdapter.getCursor(); |
| // If mTrackCursor is null, this can be because it doesn't have |
| // a cursor yet (because the initial query that sets its cursor |
| // is still in progress), or because the query failed. |
| // In order to not flash the error dialog at the user for the |
| // first case, simply retry the query when the cursor is null. |
| // Worst case, we end up doing the same query twice. |
| if (mTrackCursor != null) { |
| init(mTrackCursor, false); |
| } else { |
| setTitle(R.string.working_songs); |
| getTrackCursor(mAdapter.getQueryHandler(), null, true); |
| } |
| } |
| if (!mEditMode) { |
| MusicUtils.updateNowPlaying(this); |
| } |
| } |
| |
| public void onServiceDisconnected(ComponentName name) { |
| // we can't really function without the service, so don't |
| finish(); |
| } |
| |
| @Override |
| public Object onRetainNonConfigurationInstance() { |
| TrackListAdapter a = mAdapter; |
| mAdapterSent = true; |
| return a; |
| } |
| |
| @Override |
| public void onDestroy() { |
| ListView lv = getListView(); |
| if (lv != null) { |
| if (mUseLastListPos) { |
| mLastListPosCourse = lv.getFirstVisiblePosition(); |
| View cv = lv.getChildAt(0); |
| if (cv != null) { |
| mLastListPosFine = cv.getTop(); |
| } |
| } |
| if (mEditMode) { |
| // clear the listeners so we won't get any more callbacks |
| ((TouchInterceptor) lv).setDropListener(null); |
| ((TouchInterceptor) lv).setRemoveListener(null); |
| } |
| } |
| |
| MusicUtils.unbindFromService(mToken); |
| try { |
| if ("nowplaying".equals(mPlaylist)) { |
| unregisterReceiverSafe(mNowPlayingListener); |
| } else { |
| unregisterReceiverSafe(mTrackListListener); |
| } |
| } catch (IllegalArgumentException ex) { |
| // we end up here in case we never registered the listeners |
| } |
| |
| // If we have an adapter and didn't send it off to another activity yet, we should |
| // close its cursor, which we do by assigning a null cursor to it. Doing this |
| // instead of closing the cursor directly keeps the framework from accessing |
| // the closed cursor later. |
| if (!mAdapterSent && mAdapter != null) { |
| mAdapter.changeCursor(null); |
| } |
| // Because we pass the adapter to the next activity, we need to make |
| // sure it doesn't keep a reference to this activity. We can do this |
| // by clearing its DatasetObservers, which setListAdapter(null) does. |
| setListAdapter(null); |
| mAdapter = null; |
| unregisterReceiverSafe(mScanListener); |
| super.onDestroy(); |
| } |
| |
| /** |
| * Unregister a receiver, but eat the exception that is thrown if the |
| * receiver was never registered to begin with. This is a little easier |
| * than keeping track of whether the receivers have actually been |
| * registered by the time onDestroy() is called. |
| */ |
| private void unregisterReceiverSafe(BroadcastReceiver receiver) { |
| try { |
| unregisterReceiver(receiver); |
| } catch (IllegalArgumentException e) { |
| // ignore |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (mTrackCursor != null) { |
| getListView().invalidateViews(); |
| } |
| MusicUtils.setSpinnerState(this); |
| } |
| @Override |
| public void onPause() { |
| mReScanHandler.removeCallbacksAndMessages(null); |
| super.onPause(); |
| } |
| |
| /* |
| * This listener gets called when the media scanner starts up or finishes, and |
| * when the sd card is unmounted. |
| */ |
| private BroadcastReceiver mScanListener = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action) || |
| Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) { |
| MusicUtils.setSpinnerState(TrackBrowserActivity.this); |
| } |
| mReScanHandler.sendEmptyMessage(0); |
| } |
| }; |
| |
| private Handler mReScanHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| if (mAdapter != null) { |
| getTrackCursor(mAdapter.getQueryHandler(), null, true); |
| } |
| // if the query results in a null cursor, onQueryComplete() will |
| // call init(), which will post a delayed message to this handler |
| // in order to try again. |
| } |
| }; |
| |
| public void onSaveInstanceState(Bundle outcicle) { |
| // need to store the selected item so we don't lose it in case |
| // of an orientation switch. Otherwise we could lose it while |
| // in the middle of specifying a playlist to add the item to. |
| outcicle.putLong("selectedtrack", mSelectedId); |
| outcicle.putString("artist", mArtistId); |
| outcicle.putString("album", mAlbumId); |
| outcicle.putString("playlist", mPlaylist); |
| outcicle.putString("genre", mGenre); |
| outcicle.putBoolean("editmode", mEditMode); |
| super.onSaveInstanceState(outcicle); |
| } |
| |
| public void init(Cursor newCursor, boolean isLimited) { |
| |
| if (mAdapter == null) { |
| return; |
| } |
| mAdapter.changeCursor(newCursor); // also sets mTrackCursor |
| |
| if (mTrackCursor == null) { |
| MusicUtils.displayDatabaseError(this); |
| closeContextMenu(); |
| mReScanHandler.sendEmptyMessageDelayed(0, 1000); |
| return; |
| } |
| |
| MusicUtils.hideDatabaseError(this); |
| mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab); |
| setTitle(); |
| |
| // Restore previous position |
| if (mLastListPosCourse >= 0 && mUseLastListPos) { |
| ListView lv = getListView(); |
| // this hack is needed because otherwise the position doesn't change |
| // for the 2nd (non-limited) cursor |
| lv.setAdapter(lv.getAdapter()); |
| lv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine); |
| if (!isLimited) { |
| mLastListPosCourse = -1; |
| } |
| } |
| |
| // When showing the queue, position the selection on the currently playing track |
| // Otherwise, position the selection on the first matching artist, if any |
| IntentFilter f = new IntentFilter(); |
| f.addAction(MediaPlaybackService.META_CHANGED); |
| f.addAction(MediaPlaybackService.QUEUE_CHANGED); |
| if ("nowplaying".equals(mPlaylist)) { |
| try { |
| int cur = MusicUtils.sService.getQueuePosition(); |
| setSelection(cur); |
| registerReceiver(mNowPlayingListener, new IntentFilter(f)); |
| mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED)); |
| } catch (RemoteException ex) { |
| } |
| } else { |
| String key = getIntent().getStringExtra("artist"); |
| if (key != null) { |
| int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID); |
| mTrackCursor.moveToFirst(); |
| while (! mTrackCursor.isAfterLast()) { |
| String artist = mTrackCursor.getString(keyidx); |
| if (artist.equals(key)) { |
| setSelection(mTrackCursor.getPosition()); |
| break; |
| } |
| mTrackCursor.moveToNext(); |
| } |
| } |
| registerReceiver(mTrackListListener, new IntentFilter(f)); |
| mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED)); |
| } |
| } |
| |
| private void setAlbumArtBackground() { |
| if (!mEditMode) { |
| try { |
| long albumid = Long.valueOf(mAlbumId); |
| Bitmap bm = MusicUtils.getArtwork(TrackBrowserActivity.this, -1, albumid, false); |
| if (bm != null) { |
| MusicUtils.setBackground(mTrackList, bm); |
| mTrackList.setCacheColorHint(0); |
| return; |
| } |
| } catch (Exception ex) { |
| } |
| } |
| mTrackList.setBackgroundColor(0xff000000); |
| mTrackList.setCacheColorHint(0); |
| } |
| |
| private void setTitle() { |
| |
| CharSequence fancyName = null; |
| if (mAlbumId != null) { |
| int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0; |
| if (numresults > 0) { |
| mTrackCursor.moveToFirst(); |
| int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM); |
| fancyName = mTrackCursor.getString(idx); |
| // For compilation albums show only the album title, |
| // but for regular albums show "artist - album". |
| // To determine whether something is a compilation |
| // album, do a query for the artist + album of the |
| // first item, and see if it returns the same number |
| // of results as the album query. |
| String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId + |
| "' AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + |
| mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Media.ARTIST_ID)); |
| Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, |
| new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null); |
| if (cursor != null) { |
| if (cursor.getCount() != numresults) { |
| // compilation album |
| fancyName = mTrackCursor.getString(idx); |
| } |
| cursor.deactivate(); |
| } |
| if (fancyName == null || fancyName.equals(MediaStore.UNKNOWN_STRING)) { |
| fancyName = getString(R.string.unknown_album_name); |
| } |
| } |
| } else if (mPlaylist != null) { |
| if (mPlaylist.equals("nowplaying")) { |
| if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) { |
| fancyName = getText(R.string.partyshuffle_title); |
| } else { |
| fancyName = getText(R.string.nowplaying_title); |
| } |
| } else if (mPlaylist.equals("podcasts")){ |
| fancyName = getText(R.string.podcasts_title); |
| } else if (mPlaylist.equals("recentlyadded")){ |
| fancyName = getText(R.string.recentlyadded_title); |
| } else { |
| String [] cols = new String [] { |
| MediaStore.Audio.Playlists.NAME |
| }; |
| Cursor cursor = MusicUtils.query(this, |
| ContentUris.withAppendedId(Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)), |
| cols, null, null, null); |
| if (cursor != null) { |
| if (cursor.getCount() != 0) { |
| cursor.moveToFirst(); |
| fancyName = cursor.getString(0); |
| } |
| cursor.deactivate(); |
| } |
| } |
| } else if (mGenre != null) { |
| String [] cols = new String [] { |
| MediaStore.Audio.Genres.NAME |
| }; |
| Cursor cursor = MusicUtils.query(this, |
| ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)), |
| cols, null, null, null); |
| if (cursor != null) { |
| if (cursor.getCount() != 0) { |
| cursor.moveToFirst(); |
| fancyName = cursor.getString(0); |
| } |
| cursor.deactivate(); |
| } |
| } |
| |
| if (fancyName != null) { |
| setTitle(fancyName); |
| } else { |
| setTitle(R.string.tracks_title); |
| } |
| } |
| |
| private TouchInterceptor.DropListener mDropListener = |
| new TouchInterceptor.DropListener() { |
| public void drop(int from, int to) { |
| if (mTrackCursor instanceof NowPlayingCursor) { |
| // update the currently playing list |
| NowPlayingCursor c = (NowPlayingCursor) mTrackCursor; |
| c.moveItem(from, to); |
| ((TrackListAdapter)getListAdapter()).notifyDataSetChanged(); |
| getListView().invalidateViews(); |
| mDeletedOneRow = true; |
| } else { |
| // update a saved playlist |
| MediaStore.Audio.Playlists.Members.moveItem(getContentResolver(), |
| Long.valueOf(mPlaylist), from, to); |
| } |
| } |
| }; |
| |
| private TouchInterceptor.RemoveListener mRemoveListener = |
| new TouchInterceptor.RemoveListener() { |
| public void remove(int which) { |
| removePlaylistItem(which); |
| } |
| }; |
| |
| private void removePlaylistItem(int which) { |
| View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition()); |
| if (v == null) { |
| Log.d(LOGTAG, "No view when removing playlist item " + which); |
| return; |
| } |
| try { |
| if (MusicUtils.sService != null |
| && which != MusicUtils.sService.getQueuePosition()) { |
| mDeletedOneRow = true; |
| } |
| } catch (RemoteException e) { |
| // Service died, so nothing playing. |
| mDeletedOneRow = true; |
| } |
| v.setVisibility(View.GONE); |
| mTrackList.invalidateViews(); |
| if (mTrackCursor instanceof NowPlayingCursor) { |
| ((NowPlayingCursor)mTrackCursor).removeItem(which); |
| } else { |
| int colidx = mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Playlists.Members._ID); |
| mTrackCursor.moveToPosition(which); |
| long id = mTrackCursor.getLong(colidx); |
| Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", |
| Long.valueOf(mPlaylist)); |
| getContentResolver().delete( |
| ContentUris.withAppendedId(uri, id), null, null); |
| } |
| v.setVisibility(View.VISIBLE); |
| mTrackList.invalidateViews(); |
| } |
| |
| private BroadcastReceiver mTrackListListener = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| getListView().invalidateViews(); |
| if (!mEditMode) { |
| MusicUtils.updateNowPlaying(TrackBrowserActivity.this); |
| } |
| } |
| }; |
| |
| private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) { |
| getListView().invalidateViews(); |
| } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) { |
| if (mDeletedOneRow) { |
| // This is the notification for a single row that was |
| // deleted previously, which is already reflected in |
| // the UI. |
| mDeletedOneRow = false; |
| return; |
| } |
| // The service could disappear while the broadcast was in flight, |
| // so check to see if it's still valid |
| if (MusicUtils.sService == null) { |
| finish(); |
| return; |
| } |
| if (mAdapter != null) { |
| Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols); |
| if (c.getCount() == 0) { |
| finish(); |
| return; |
| } |
| mAdapter.changeCursor(c); |
| } |
| } |
| } |
| }; |
| |
| // Cursor should be positioned on the entry to be checked |
| // Returns false if the entry matches the naming pattern used for recordings, |
| // or if it is marked as not music in the database. |
| private boolean isMusic(Cursor c) { |
| int titleidx = c.getColumnIndex(MediaStore.Audio.Media.TITLE); |
| int albumidx = c.getColumnIndex(MediaStore.Audio.Media.ALBUM); |
| int artistidx = c.getColumnIndex(MediaStore.Audio.Media.ARTIST); |
| |
| String title = c.getString(titleidx); |
| String album = c.getString(albumidx); |
| String artist = c.getString(artistidx); |
| if (MediaStore.UNKNOWN_STRING.equals(album) && |
| MediaStore.UNKNOWN_STRING.equals(artist) && |
| title != null && |
| title.startsWith("recording")) { |
| // not music |
| return false; |
| } |
| |
| int ismusic_idx = c.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC); |
| boolean ismusic = true; |
| if (ismusic_idx >= 0) { |
| ismusic = mTrackCursor.getInt(ismusic_idx) != 0; |
| } |
| return ismusic; |
| } |
| |
| @Override |
| public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) { |
| menu.add(0, PLAY_SELECTION, 0, R.string.play_selection); |
| SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist); |
| MusicUtils.makePlaylistMenu(this, sub); |
| if (mEditMode) { |
| menu.add(0, REMOVE, 0, R.string.remove_from_playlist); |
| } |
| menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu); |
| menu.add(0, DELETE_ITEM, 0, R.string.delete_item); |
| AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn; |
| mSelectedPosition = mi.position; |
| mTrackCursor.moveToPosition(mSelectedPosition); |
| try { |
| int id_idx = mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Playlists.Members.AUDIO_ID); |
| mSelectedId = mTrackCursor.getLong(id_idx); |
| } catch (IllegalArgumentException ex) { |
| mSelectedId = mi.id; |
| } |
| // only add the 'search' menu if the selected item is music |
| if (isMusic(mTrackCursor)) { |
| menu.add(0, SEARCH, 0, R.string.search_title); |
| } |
| mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Media.ALBUM)); |
| mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Media.ARTIST)); |
| mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Media.TITLE)); |
| menu.setHeaderTitle(mCurrentTrackName); |
| } |
| |
| @Override |
| public boolean onContextItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case PLAY_SELECTION: { |
| // play the track |
| int position = mSelectedPosition; |
| MusicUtils.playAll(this, mTrackCursor, position); |
| return true; |
| } |
| |
| case QUEUE: { |
| long [] list = new long[] { mSelectedId }; |
| MusicUtils.addToCurrentPlaylist(this, list); |
| return true; |
| } |
| |
| case NEW_PLAYLIST: { |
| Intent intent = new Intent(); |
| intent.setClass(this, CreatePlaylist.class); |
| startActivityForResult(intent, NEW_PLAYLIST); |
| return true; |
| } |
| |
| case PLAYLIST_SELECTED: { |
| long [] list = new long[] { mSelectedId }; |
| long playlist = item.getIntent().getLongExtra("playlist", 0); |
| MusicUtils.addToPlaylist(this, list, playlist); |
| return true; |
| } |
| |
| case USE_AS_RINGTONE: |
| // Set the system setting to make this the current ringtone |
| MusicUtils.setRingtone(this, mSelectedId); |
| return true; |
| |
| case DELETE_ITEM: { |
| long [] list = new long[1]; |
| list[0] = (int) mSelectedId; |
| Bundle b = new Bundle(); |
| String f; |
| if (android.os.Environment.isExternalStorageRemovable()) { |
| f = getString(R.string.delete_song_desc); |
| } else { |
| f = getString(R.string.delete_song_desc_nosdcard); |
| } |
| String desc = String.format(f, mCurrentTrackName); |
| b.putString("description", desc); |
| b.putLongArray("items", list); |
| Intent intent = new Intent(); |
| intent.setClass(this, DeleteItems.class); |
| intent.putExtras(b); |
| startActivityForResult(intent, -1); |
| return true; |
| } |
| |
| case REMOVE: |
| removePlaylistItem(mSelectedPosition); |
| return true; |
| |
| case SEARCH: |
| doSearch(); |
| return true; |
| } |
| return super.onContextItemSelected(item); |
| } |
| |
| void doSearch() { |
| CharSequence title = null; |
| String query = null; |
| |
| Intent i = new Intent(); |
| i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH); |
| i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| title = mCurrentTrackName; |
| if (MediaStore.UNKNOWN_STRING.equals(mCurrentArtistNameForAlbum)) { |
| query = mCurrentTrackName; |
| } else { |
| query = mCurrentArtistNameForAlbum + " " + mCurrentTrackName; |
| i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum); |
| } |
| if (MediaStore.UNKNOWN_STRING.equals(mCurrentAlbumName)) { |
| i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName); |
| } |
| i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*"); |
| title = getString(R.string.mediasearch, title); |
| i.putExtra(SearchManager.QUERY, query); |
| |
| startActivity(Intent.createChooser(i, title)); |
| } |
| |
| // In order to use alt-up/down as a shortcut for moving the selected item |
| // in the list, we need to override dispatchKeyEvent, not onKeyDown. |
| // (onKeyDown never sees these events, since they are handled by the list) |
| @Override |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| int curpos = mTrackList.getSelectedItemPosition(); |
| if (mPlaylist != null && !mPlaylist.equals("recentlyadded") && curpos >= 0 && |
| event.getMetaState() != 0 && event.getAction() == KeyEvent.ACTION_DOWN) { |
| switch (event.getKeyCode()) { |
| case KeyEvent.KEYCODE_DPAD_UP: |
| moveItem(true); |
| return true; |
| case KeyEvent.KEYCODE_DPAD_DOWN: |
| moveItem(false); |
| return true; |
| case KeyEvent.KEYCODE_DEL: |
| removeItem(); |
| return true; |
| } |
| } |
| |
| return super.dispatchKeyEvent(event); |
| } |
| |
| private void removeItem() { |
| int curcount = mTrackCursor.getCount(); |
| int curpos = mTrackList.getSelectedItemPosition(); |
| if (curcount == 0 || curpos < 0) { |
| return; |
| } |
| |
| if ("nowplaying".equals(mPlaylist)) { |
| // remove track from queue |
| |
| // Work around bug 902971. To get quick visual feedback |
| // of the deletion of the item, hide the selected view. |
| try { |
| if (curpos != MusicUtils.sService.getQueuePosition()) { |
| mDeletedOneRow = true; |
| } |
| } catch (RemoteException ex) { |
| } |
| View v = mTrackList.getSelectedView(); |
| v.setVisibility(View.GONE); |
| mTrackList.invalidateViews(); |
| ((NowPlayingCursor)mTrackCursor).removeItem(curpos); |
| v.setVisibility(View.VISIBLE); |
| mTrackList.invalidateViews(); |
| } else { |
| // remove track from playlist |
| int colidx = mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Playlists.Members._ID); |
| mTrackCursor.moveToPosition(curpos); |
| long id = mTrackCursor.getLong(colidx); |
| Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", |
| Long.valueOf(mPlaylist)); |
| getContentResolver().delete( |
| ContentUris.withAppendedId(uri, id), null, null); |
| curcount--; |
| if (curcount == 0) { |
| finish(); |
| } else { |
| mTrackList.setSelection(curpos < curcount ? curpos : curcount); |
| } |
| } |
| } |
| |
| private void moveItem(boolean up) { |
| int curcount = mTrackCursor.getCount(); |
| int curpos = mTrackList.getSelectedItemPosition(); |
| if ( (up && curpos < 1) || (!up && curpos >= curcount - 1)) { |
| return; |
| } |
| |
| if (mTrackCursor instanceof NowPlayingCursor) { |
| NowPlayingCursor c = (NowPlayingCursor) mTrackCursor; |
| c.moveItem(curpos, up ? curpos - 1 : curpos + 1); |
| ((TrackListAdapter)getListAdapter()).notifyDataSetChanged(); |
| getListView().invalidateViews(); |
| mDeletedOneRow = true; |
| if (up) { |
| mTrackList.setSelection(curpos - 1); |
| } else { |
| mTrackList.setSelection(curpos + 1); |
| } |
| } else { |
| int colidx = mTrackCursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Playlists.Members.PLAY_ORDER); |
| mTrackCursor.moveToPosition(curpos); |
| int currentplayidx = mTrackCursor.getInt(colidx); |
| Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external", |
| Long.valueOf(mPlaylist)); |
| ContentValues values = new ContentValues(); |
| String where = MediaStore.Audio.Playlists.Members._ID + "=?"; |
| String [] wherearg = new String[1]; |
| ContentResolver res = getContentResolver(); |
| if (up) { |
| values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1); |
| wherearg[0] = mTrackCursor.getString(0); |
| res.update(baseUri, values, where, wherearg); |
| mTrackCursor.moveToPrevious(); |
| } else { |
| values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1); |
| wherearg[0] = mTrackCursor.getString(0); |
| res.update(baseUri, values, where, wherearg); |
| mTrackCursor.moveToNext(); |
| } |
| values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx); |
| wherearg[0] = mTrackCursor.getString(0); |
| res.update(baseUri, values, where, wherearg); |
| } |
| } |
| |
| @Override |
| protected void onListItemClick(ListView l, View v, int position, long id) |
| { |
| if (mTrackCursor.getCount() == 0) { |
| return; |
| } |
| // When selecting a track from the queue, just jump there instead of |
| // reloading the queue. This is both faster, and prevents accidentally |
| // dropping out of party shuffle. |
| if (mTrackCursor instanceof NowPlayingCursor) { |
| if (MusicUtils.sService != null) { |
| try { |
| MusicUtils.sService.setQueuePosition(position); |
| return; |
| } catch (RemoteException ex) { |
| } |
| } |
| } |
| MusicUtils.playAll(this, mTrackCursor, position); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| /* This activity is used for a number of different browsing modes, and the menu can |
| * be different for each of them: |
| * - all tracks, optionally restricted to an album, artist or playlist |
| * - the list of currently playing songs |
| */ |
| super.onCreateOptionsMenu(menu); |
| if (mPlaylist == null) { |
| menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(R.drawable.ic_menu_play_clip); |
| } |
| menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu() |
| menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle); |
| if (mPlaylist != null) { |
| menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(android.R.drawable.ic_menu_save); |
| if (mPlaylist.equals("nowplaying")) { |
| menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist).setIcon(R.drawable.ic_menu_clear_playlist); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| MusicUtils.setPartyShuffleMenuIcon(menu); |
| return super.onPrepareOptionsMenu(menu); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| Intent intent; |
| Cursor cursor; |
| switch (item.getItemId()) { |
| case PLAY_ALL: { |
| MusicUtils.playAll(this, mTrackCursor); |
| return true; |
| } |
| |
| case PARTY_SHUFFLE: |
| MusicUtils.togglePartyShuffle(); |
| break; |
| |
| case SHUFFLE_ALL: |
| // Should 'shuffle all' shuffle ALL, or only the tracks shown? |
| cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, |
| new String [] { MediaStore.Audio.Media._ID}, |
| MediaStore.Audio.Media.IS_MUSIC + "=1", null, |
| MediaStore.Audio.Media.DEFAULT_SORT_ORDER); |
| if (cursor != null) { |
| MusicUtils.shuffleAll(this, cursor); |
| cursor.close(); |
| } |
| return true; |
| |
| case SAVE_AS_PLAYLIST: |
| intent = new Intent(); |
| intent.setClass(this, CreatePlaylist.class); |
| startActivityForResult(intent, SAVE_AS_PLAYLIST); |
| return true; |
| |
| case CLEAR_PLAYLIST: |
| // We only clear the current playlist |
| MusicUtils.clearQueue(); |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent intent) { |
| switch (requestCode) { |
| case SCAN_DONE: |
| if (resultCode == RESULT_CANCELED) { |
| finish(); |
| } else { |
| getTrackCursor(mAdapter.getQueryHandler(), null, true); |
| } |
| break; |
| |
| case NEW_PLAYLIST: |
| if (resultCode == RESULT_OK) { |
| Uri uri = intent.getData(); |
| if (uri != null) { |
| long [] list = new long[] { mSelectedId }; |
| MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment())); |
| } |
| } |
| break; |
| |
| case SAVE_AS_PLAYLIST: |
| if (resultCode == RESULT_OK) { |
| Uri uri = intent.getData(); |
| if (uri != null) { |
| long [] list = MusicUtils.getSongListForCursor(mTrackCursor); |
| int plid = Integer.parseInt(uri.getLastPathSegment()); |
| MusicUtils.addToPlaylist(this, list, plid); |
| } |
| } |
| break; |
| } |
| } |
| |
| private Cursor getTrackCursor(TrackListAdapter.TrackQueryHandler queryhandler, String filter, |
| boolean async) { |
| |
| if (queryhandler == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| Cursor ret = null; |
| mSortOrder = MediaStore.Audio.Media.TITLE_KEY; |
| StringBuilder where = new StringBuilder(); |
| where.append(MediaStore.Audio.Media.TITLE + " != ''"); |
| |
| if (mGenre != null) { |
| Uri uri = MediaStore.Audio.Genres.Members.getContentUri("external", |
| Integer.valueOf(mGenre)); |
| if (!TextUtils.isEmpty(filter)) { |
| uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build(); |
| } |
| mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER; |
| ret = queryhandler.doQuery(uri, |
| mCursorCols, where.toString(), null, mSortOrder, async); |
| } else if (mPlaylist != null) { |
| if (mPlaylist.equals("nowplaying")) { |
| if (MusicUtils.sService != null) { |
| ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols); |
| if (ret.getCount() == 0) { |
| finish(); |
| } |
| } else { |
| // Nothing is playing. |
| } |
| } else if (mPlaylist.equals("podcasts")) { |
| where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1"); |
| Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; |
| if (!TextUtils.isEmpty(filter)) { |
| uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build(); |
| } |
| ret = queryhandler.doQuery(uri, |
| mCursorCols, where.toString(), null, |
| MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async); |
| } else if (mPlaylist.equals("recentlyadded")) { |
| // do a query for all songs added in the last X weeks |
| Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; |
| if (!TextUtils.isEmpty(filter)) { |
| uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build(); |
| } |
| int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7); |
| where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">"); |
| where.append(System.currentTimeMillis() / 1000 - X); |
| ret = queryhandler.doQuery(uri, |
| mCursorCols, where.toString(), null, |
| MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async); |
| } else { |
| Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", |
| Long.valueOf(mPlaylist)); |
| if (!TextUtils.isEmpty(filter)) { |
| uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build(); |
| } |
| mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER; |
| ret = queryhandler.doQuery(uri, mPlaylistMemberCols, |
| where.toString(), null, mSortOrder, async); |
| } |
| } else { |
| if (mAlbumId != null) { |
| where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId); |
| mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder; |
| } |
| if (mArtistId != null) { |
| where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId); |
| } |
| where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1"); |
| Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; |
| if (!TextUtils.isEmpty(filter)) { |
| uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build(); |
| } |
| ret = queryhandler.doQuery(uri, |
| mCursorCols, where.toString() , null, mSortOrder, async); |
| } |
| |
| // This special case is for the "nowplaying" cursor, which cannot be handled |
| // asynchronously using AsyncQueryHandler, so we do some extra initialization here. |
| if (ret != null && async) { |
| init(ret, false); |
| setTitle(); |
| } |
| return ret; |
| } |
| |
| private class NowPlayingCursor extends AbstractCursor |
| { |
| public NowPlayingCursor(IMediaPlaybackService service, String [] cols) |
| { |
| mCols = cols; |
| mService = service; |
| makeNowPlayingCursor(); |
| } |
| private void makeNowPlayingCursor() { |
| mCurrentPlaylistCursor = null; |
| try { |
| mNowPlaying = mService.getQueue(); |
| } catch (RemoteException ex) { |
| mNowPlaying = new long[0]; |
| } |
| mSize = mNowPlaying.length; |
| if (mSize == 0) { |
| return; |
| } |
| |
| StringBuilder where = new StringBuilder(); |
| where.append(MediaStore.Audio.Media._ID + " IN ("); |
| for (int i = 0; i < mSize; i++) { |
| where.append(mNowPlaying[i]); |
| if (i < mSize - 1) { |
| where.append(","); |
| } |
| } |
| where.append(")"); |
| |
| mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this, |
| MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, |
| mCols, where.toString(), null, MediaStore.Audio.Media._ID); |
| |
| if (mCurrentPlaylistCursor == null) { |
| mSize = 0; |
| return; |
| } |
| |
| int size = mCurrentPlaylistCursor.getCount(); |
| mCursorIdxs = new long[size]; |
| mCurrentPlaylistCursor.moveToFirst(); |
| int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); |
| for (int i = 0; i < size; i++) { |
| mCursorIdxs[i] = mCurrentPlaylistCursor.getLong(colidx); |
| mCurrentPlaylistCursor.moveToNext(); |
| } |
| mCurrentPlaylistCursor.moveToFirst(); |
| mCurPos = -1; |
| |
| // At this point we can verify the 'now playing' list we got |
| // earlier to make sure that all the items in there still exist |
| // in the database, and remove those that aren't. This way we |
| // don't get any blank items in the list. |
| try { |
| int removed = 0; |
| for (int i = mNowPlaying.length - 1; i >= 0; i--) { |
| long trackid = mNowPlaying[i]; |
| int crsridx = Arrays.binarySearch(mCursorIdxs, trackid); |
| if (crsridx < 0) { |
| //Log.i("@@@@@", "item no longer exists in db: " + trackid); |
| removed += mService.removeTrack(trackid); |
| } |
| } |
| if (removed > 0) { |
| mNowPlaying = mService.getQueue(); |
| mSize = mNowPlaying.length; |
| if (mSize == 0) { |
| mCursorIdxs = null; |
| return; |
| } |
| } |
| } catch (RemoteException ex) { |
| mNowPlaying = new long[0]; |
| } |
| } |
| |
| @Override |
| public int getCount() |
| { |
| return mSize; |
| } |
| |
| @Override |
| public boolean onMove(int oldPosition, int newPosition) |
| { |
| if (oldPosition == newPosition) |
| return true; |
| |
| if (mNowPlaying == null || mCursorIdxs == null || newPosition >= mNowPlaying.length) { |
| return false; |
| } |
| |
| // The cursor doesn't have any duplicates in it, and is not ordered |
| // in queue-order, so we need to figure out where in the cursor we |
| // should be. |
| |
| long newid = mNowPlaying[newPosition]; |
| int crsridx = Arrays.binarySearch(mCursorIdxs, newid); |
| mCurrentPlaylistCursor.moveToPosition(crsridx); |
| mCurPos = newPosition; |
| |
| return true; |
| } |
| |
| public boolean removeItem(int which) |
| { |
| try { |
| if (mService.removeTracks(which, which) == 0) { |
| return false; // delete failed |
| } |
| int i = (int) which; |
| mSize--; |
| while (i < mSize) { |
| mNowPlaying[i] = mNowPlaying[i+1]; |
| i++; |
| } |
| onMove(-1, (int) mCurPos); |
| } catch (RemoteException ex) { |
| } |
| return true; |
| } |
| |
| public void moveItem(int from, int to) { |
| try { |
| mService.moveQueueItem(from, to); |
| mNowPlaying = mService.getQueue(); |
| onMove(-1, mCurPos); // update the underlying cursor |
| } catch (RemoteException ex) { |
| } |
| } |
| |
| private void dump() { |
| String where = "("; |
| for (int i = 0; i < mSize; i++) { |
| where += mNowPlaying[i]; |
| if (i < mSize - 1) { |
| where += ","; |
| } |
| } |
| where += ")"; |
| Log.i("NowPlayingCursor: ", where); |
| } |
| |
| @Override |
| public String getString(int column) |
| { |
| try { |
| return mCurrentPlaylistCursor.getString(column); |
| } catch (Exception ex) { |
| onChange(true); |
| return ""; |
| } |
| } |
| |
| @Override |
| public short getShort(int column) |
| { |
| return mCurrentPlaylistCursor.getShort(column); |
| } |
| |
| @Override |
| public int getInt(int column) |
| { |
| try { |
| return mCurrentPlaylistCursor.getInt(column); |
| } catch (Exception ex) { |
| onChange(true); |
| return 0; |
| } |
| } |
| |
| @Override |
| public long getLong(int column) |
| { |
| try { |
| return mCurrentPlaylistCursor.getLong(column); |
| } catch (Exception ex) { |
| onChange(true); |
| return 0; |
| } |
| } |
| |
| @Override |
| public float getFloat(int column) |
| { |
| return mCurrentPlaylistCursor.getFloat(column); |
| } |
| |
| @Override |
| public double getDouble(int column) |
| { |
| return mCurrentPlaylistCursor.getDouble(column); |
| } |
| |
| @Override |
| public int getType(int column) { |
| return mCurrentPlaylistCursor.getType(column); |
| } |
| |
| @Override |
| public boolean isNull(int column) |
| { |
| return mCurrentPlaylistCursor.isNull(column); |
| } |
| |
| @Override |
| public String[] getColumnNames() |
| { |
| return mCols; |
| } |
| |
| @Override |
| public void deactivate() |
| { |
| if (mCurrentPlaylistCursor != null) |
| mCurrentPlaylistCursor.deactivate(); |
| } |
| |
| @Override |
| public boolean requery() |
| { |
| makeNowPlayingCursor(); |
| return true; |
| } |
| |
| private String [] mCols; |
| private Cursor mCurrentPlaylistCursor; // updated in onMove |
| private int mSize; // size of the queue |
| private long[] mNowPlaying; |
| private long[] mCursorIdxs; |
| private int mCurPos; |
| private IMediaPlaybackService mService; |
| } |
| |
| static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer { |
| boolean mIsNowPlaying; |
| boolean mDisableNowPlayingIndicator; |
| |
| int mTitleIdx; |
| int mArtistIdx; |
| int mDurationIdx; |
| int mAudioIdIdx; |
| |
| private final StringBuilder mBuilder = new StringBuilder(); |
| private final String mUnknownArtist; |
| private final String mUnknownAlbum; |
| |
| private AlphabetIndexer mIndexer; |
| |
| private TrackBrowserActivity mActivity = null; |
| private TrackQueryHandler mQueryHandler; |
| private String mConstraint = null; |
| private boolean mConstraintIsValid = false; |
| |
| static class ViewHolder { |
| TextView line1; |
| TextView line2; |
| TextView duration; |
| ImageView play_indicator; |
| CharArrayBuffer buffer1; |
| char [] buffer2; |
| } |
| |
| class TrackQueryHandler extends AsyncQueryHandler { |
| |
| class QueryArgs { |
| public Uri uri; |
| public String [] projection; |
| public String selection; |
| public String [] selectionArgs; |
| public String orderBy; |
| } |
| |
| TrackQueryHandler(ContentResolver res) { |
| super(res); |
| } |
| |
| public Cursor doQuery(Uri uri, String[] projection, |
| String selection, String[] selectionArgs, |
| String orderBy, boolean async) { |
| if (async) { |
| // Get 100 results first, which is enough to allow the user to start scrolling, |
| // while still being very fast. |
| Uri limituri = uri.buildUpon().appendQueryParameter("limit", "100").build(); |
| QueryArgs args = new QueryArgs(); |
| args.uri = uri; |
| args.projection = projection; |
| args.selection = selection; |
| args.selectionArgs = selectionArgs; |
| args.orderBy = orderBy; |
| |
| startQuery(0, args, limituri, projection, selection, selectionArgs, orderBy); |
| return null; |
| } |
| return MusicUtils.query(mActivity, |
| uri, projection, selection, selectionArgs, orderBy); |
| } |
| |
| @Override |
| protected void onQueryComplete(int token, Object cookie, Cursor cursor) { |
| //Log.i("@@@", "query complete: " + cursor.getCount() + " " + mActivity); |
| mActivity.init(cursor, cookie != null); |
| if (token == 0 && cookie != null && cursor != null && cursor.getCount() >= 100) { |
| QueryArgs args = (QueryArgs) cookie; |
| startQuery(1, null, args.uri, args.projection, args.selection, |
| args.selectionArgs, args.orderBy); |
| } |
| } |
| } |
| |
| TrackListAdapter(Context context, TrackBrowserActivity currentactivity, |
| int layout, Cursor cursor, String[] from, int[] to, |
| boolean isnowplaying, boolean disablenowplayingindicator) { |
| super(context, layout, cursor, from, to); |
| mActivity = currentactivity; |
| getColumnIndices(cursor); |
| mIsNowPlaying = isnowplaying; |
| mDisableNowPlayingIndicator = disablenowplayingindicator; |
| mUnknownArtist = context.getString(R.string.unknown_artist_name); |
| mUnknownAlbum = context.getString(R.string.unknown_album_name); |
| |
| mQueryHandler = new TrackQueryHandler(context.getContentResolver()); |
| } |
| |
| public void setActivity(TrackBrowserActivity newactivity) { |
| mActivity = newactivity; |
| } |
| |
| public TrackQueryHandler getQueryHandler() { |
| return mQueryHandler; |
| } |
| |
| private void getColumnIndices(Cursor cursor) { |
| if (cursor != null) { |
| mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE); |
| mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST); |
| mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION); |
| try { |
| mAudioIdIdx = cursor.getColumnIndexOrThrow( |
| MediaStore.Audio.Playlists.Members.AUDIO_ID); |
| } catch (IllegalArgumentException ex) { |
| mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); |
| } |
| |
| if (mIndexer != null) { |
| mIndexer.setCursor(cursor); |
| } else if (!mActivity.mEditMode && mActivity.mAlbumId == null) { |
| String alpha = mActivity.getString(R.string.fast_scroll_alphabet); |
| |
| mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha); |
| } |
| } |
| } |
| |
| @Override |
| public View newView(Context context, Cursor cursor, ViewGroup parent) { |
| View v = super.newView(context, cursor, parent); |
| ImageView iv = (ImageView) v.findViewById(R.id.icon); |
| iv.setVisibility(View.GONE); |
| |
| 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.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(mArtistIdx); |
| if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) { |
| 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); |
| |
| ImageView iv = vh.play_indicator; |
| long id = -1; |
| if (MusicUtils.sService != null) { |
| // TODO: IPC call on each bind?? |
| try { |
| if (mIsNowPlaying) { |
| id = MusicUtils.sService.getQueuePosition(); |
| } else { |
| id = MusicUtils.sService.getAudioId(); |
| } |
| } catch (RemoteException ex) { |
| } |
| } |
| |
| // Determining whether and where to show the "now playing indicator |
| // is tricky, because we don't actually keep track of where the songs |
| // in the current playlist came from after they've started playing. |
| // |
| // If the "current playlists" is shown, then we can simply match by position, |
| // otherwise, we need to match by id. Match-by-id gets a little weird if |
| // a song appears in a playlist more than once, and you're in edit-playlist |
| // mode. In that case, both items will have the "now playing" indicator. |
| // For this reason, we don't show the play indicator at all when in edit |
| // playlist mode (except when you're viewing the "current playlist", |
| // which is not really a playlist) |
| if ( (mIsNowPlaying && cursor.getPosition() == id) || |
| (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getLong(mAudioIdIdx) == id)) { |
| iv.setImageResource(R.drawable.indicator_ic_mp_playing_list); |
| iv.setVisibility(View.VISIBLE); |
| } else { |
| iv.setVisibility(View.GONE); |
| } |
| } |
| |
| @Override |
| public void changeCursor(Cursor cursor) { |
| if (mActivity.isFinishing() && cursor != null) { |
| cursor.close(); |
| cursor = null; |
| } |
| if (cursor != mActivity.mTrackCursor) { |
| mActivity.mTrackCursor = cursor; |
| super.changeCursor(cursor); |
| getColumnIndices(cursor); |
| } |
| } |
| |
| @Override |
| public Cursor runQueryOnBackgroundThread(CharSequence constraint) { |
| String s = constraint.toString(); |
| if (mConstraintIsValid && ( |
| (s == null && mConstraint == null) || |
| (s != null && s.equals(mConstraint)))) { |
| return getCursor(); |
| } |
| Cursor c = mActivity.getTrackCursor(mQueryHandler, s, false); |
| mConstraint = s; |
| mConstraintIsValid = true; |
| return c; |
| } |
| |
| // SectionIndexer methods |
| |
| public Object[] getSections() { |
| if (mIndexer != null) { |
| return mIndexer.getSections(); |
| } else { |
| return new String [] { " " }; |
| } |
| } |
| |
| public int getPositionForSection(int section) { |
| if (mIndexer != null) { |
| return mIndexer.getPositionForSection(section); |
| } |
| return 0; |
| } |
| |
| public int getSectionForPosition(int position) { |
| return 0; |
| } |
| } |
| } |
| |