blob: f697b53b3dc16aa3cadab681881e3a5db24f840c [file] [log] [blame]
The Android Open Source Project792a2202009-03-03 19:32:30 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.music;
18
Marco Nelissenf33a5752010-01-27 15:35:43 -080019import com.android.music.MusicUtils.ServiceToken;
20
The Android Open Source Project792a2202009-03-03 19:32:30 -080021import android.app.ListActivity;
22import android.app.SearchManager;
23import android.content.AsyncQueryHandler;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.ContentUris;
28import android.content.ContentValues;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.ServiceConnection;
33import android.database.AbstractCursor;
34import android.database.CharArrayBuffer;
35import android.database.Cursor;
Marco Nelissen3193d7d2009-12-14 15:31:28 -080036import android.graphics.Bitmap;
The Android Open Source Project792a2202009-03-03 19:32:30 -080037import android.media.AudioManager;
The Android Open Source Project792a2202009-03-03 19:32:30 -080038import android.net.Uri;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.IBinder;
42import android.os.Message;
43import android.os.RemoteException;
44import android.provider.MediaStore;
45import android.provider.MediaStore.Audio.Playlists;
Marco Nelissen89f6c662010-09-02 15:20:23 -070046import android.text.TextUtils;
The Android Open Source Project792a2202009-03-03 19:32:30 -080047import android.util.Log;
48import android.view.ContextMenu;
49import android.view.KeyEvent;
50import android.view.Menu;
51import android.view.MenuItem;
52import android.view.SubMenu;
53import android.view.View;
54import android.view.ViewGroup;
55import android.view.Window;
56import android.view.ContextMenu.ContextMenuInfo;
57import android.widget.AlphabetIndexer;
58import android.widget.ImageView;
59import android.widget.ListView;
60import android.widget.SectionIndexer;
61import android.widget.SimpleCursorAdapter;
62import android.widget.TextView;
63import android.widget.AdapterView.AdapterContextMenuInfo;
64
65import java.text.Collator;
66import java.util.Arrays;
67
68public class TrackBrowserActivity extends ListActivity
Jack Heb0fba8b2017-01-26 15:54:38 -080069 implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection {
Marco Nelissen756c3f52009-05-14 10:07:23 -070070 private static final int Q_SELECTED = CHILD_MENU_BASE;
71 private static final int Q_ALL = CHILD_MENU_BASE + 1;
72 private static final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
73 private static final int PLAY_ALL = CHILD_MENU_BASE + 3;
74 private static final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
75 private static final int REMOVE = CHILD_MENU_BASE + 5;
76 private static final int SEARCH = CHILD_MENU_BASE + 6;
The Android Open Source Project792a2202009-03-03 19:32:30 -080077
The Android Open Source Project792a2202009-03-03 19:32:30 -080078 private static final String LOGTAG = "TrackBrowser";
79
80 private String[] mCursorCols;
81 private String[] mPlaylistMemberCols;
82 private boolean mDeletedOneRow = false;
83 private boolean mEditMode = false;
84 private String mCurrentTrackName;
85 private String mCurrentAlbumName;
86 private String mCurrentArtistNameForAlbum;
87 private ListView mTrackList;
88 private Cursor mTrackCursor;
89 private TrackListAdapter mAdapter;
90 private boolean mAdapterSent = false;
91 private String mAlbumId;
92 private String mArtistId;
93 private String mPlaylist;
94 private String mGenre;
95 private String mSortOrder;
96 private int mSelectedPosition;
97 private long mSelectedId;
Marco Nelissenec0c57a2009-12-12 12:27:11 -080098 private static int mLastListPosCourse = -1;
99 private static int mLastListPosFine = -1;
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800100 private boolean mUseLastListPos = false;
Marco Nelissenf33a5752010-01-27 15:35:43 -0800101 private ServiceToken mToken;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800102
Jack Heb0fba8b2017-01-26 15:54:38 -0800103 public TrackBrowserActivity() {}
The Android Open Source Project792a2202009-03-03 19:32:30 -0800104
105 /** Called when the activity is first created. */
106 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -0800107 public void onCreate(Bundle icicle) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800108 super.onCreate(icicle);
109 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800110 Intent intent = getIntent();
111 if (intent != null) {
112 if (intent.getBooleanExtra("withtabs", false)) {
113 requestWindowFeature(Window.FEATURE_NO_TITLE);
114 }
115 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800116 setVolumeControlStream(AudioManager.STREAM_MUSIC);
117 if (icicle != null) {
118 mSelectedId = icicle.getLong("selectedtrack");
119 mAlbumId = icicle.getString("album");
120 mArtistId = icicle.getString("artist");
121 mPlaylist = icicle.getString("playlist");
122 mGenre = icicle.getString("genre");
123 mEditMode = icicle.getBoolean("editmode", false);
124 } else {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800125 mAlbumId = intent.getStringExtra("album");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800126 // If we have an album, show everything on the album, not just stuff
127 // by a particular artist.
The Android Open Source Project792a2202009-03-03 19:32:30 -0800128 mArtistId = intent.getStringExtra("artist");
129 mPlaylist = intent.getStringExtra("playlist");
130 mGenre = intent.getStringExtra("genre");
131 mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
132 }
133
Jack Heb0fba8b2017-01-26 15:54:38 -0800134 mCursorCols = new String[] {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
135 MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM,
136 MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ARTIST_ID,
137 MediaStore.Audio.Media.DURATION};
138 mPlaylistMemberCols = new String[] {MediaStore.Audio.Playlists.Members._ID,
139 MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
140 MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST,
141 MediaStore.Audio.Media.ARTIST_ID, MediaStore.Audio.Media.DURATION,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800142 MediaStore.Audio.Playlists.Members.PLAY_ORDER,
Jack Heb0fba8b2017-01-26 15:54:38 -0800143 MediaStore.Audio.Playlists.Members.AUDIO_ID, MediaStore.Audio.Media.IS_MUSIC};
The Android Open Source Project792a2202009-03-03 19:32:30 -0800144
145 setContentView(R.layout.media_picker_activity);
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800146 mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800147 mTrackList = getListView();
148 mTrackList.setOnCreateContextMenuListener(this);
Marco Nelissen355e1342010-07-30 17:34:03 -0700149 mTrackList.setCacheColorHint(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800150 if (mEditMode) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800151 ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
152 ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
Marco Nelissen355e1342010-07-30 17:34:03 -0700153 mTrackList.setDivider(null);
154 mTrackList.setSelector(R.drawable.list_selector_background);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800155 } else {
156 mTrackList.setTextFilterEnabled(true);
157 }
158 mAdapter = (TrackListAdapter) getLastNonConfigurationInstance();
Jack Heb0fba8b2017-01-26 15:54:38 -0800159
The Android Open Source Project792a2202009-03-03 19:32:30 -0800160 if (mAdapter != null) {
161 mAdapter.setActivity(this);
162 setListAdapter(mAdapter);
163 }
Marco Nelissenf33a5752010-01-27 15:35:43 -0800164 mToken = MusicUtils.bindToService(this, this);
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800165
166 // don't set the album art until after the view has been layed out
167 mTrackList.post(new Runnable() {
168
169 public void run() {
170 setAlbumArtBackground();
171 }
172 });
The Android Open Source Project792a2202009-03-03 19:32:30 -0800173 }
174
Jack Heb0fba8b2017-01-26 15:54:38 -0800175 public void onServiceConnected(ComponentName name, IBinder service) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800176 IntentFilter f = new IntentFilter();
177 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
178 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
179 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
180 f.addDataScheme("file");
181 registerReceiver(mScanListener, f);
182
183 if (mAdapter == null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800184 // Log.i("@@@", "starting query");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800185 mAdapter = new TrackListAdapter(
186 getApplication(), // need to use application context to avoid leaks
Jack Heb0fba8b2017-01-26 15:54:38 -0800187 this, mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800188 null, // cursor
Jack Heb0fba8b2017-01-26 15:54:38 -0800189 new String[] {}, new int[] {}, "nowplaying".equals(mPlaylist), mPlaylist != null
190 && !(mPlaylist.equals("podcasts")
191 || mPlaylist.equals("recentlyadded")));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800192 setListAdapter(mAdapter);
193 setTitle(R.string.working_songs);
Marco Nelissen4248ed22009-07-30 08:28:49 -0700194 getTrackCursor(mAdapter.getQueryHandler(), null, true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800195 } else {
196 mTrackCursor = mAdapter.getCursor();
197 // If mTrackCursor is null, this can be because it doesn't have
198 // a cursor yet (because the initial query that sets its cursor
199 // is still in progress), or because the query failed.
200 // In order to not flash the error dialog at the user for the
201 // first case, simply retry the query when the cursor is null.
202 // Worst case, we end up doing the same query twice.
203 if (mTrackCursor != null) {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800204 init(mTrackCursor, false);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800205 } else {
206 setTitle(R.string.working_songs);
Marco Nelissen4248ed22009-07-30 08:28:49 -0700207 getTrackCursor(mAdapter.getQueryHandler(), null, true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800208 }
209 }
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800210 if (!mEditMode) {
211 MusicUtils.updateNowPlaying(this);
212 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800213 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800214
The Android Open Source Project792a2202009-03-03 19:32:30 -0800215 public void onServiceDisconnected(ComponentName name) {
216 // we can't really function without the service, so don't
217 finish();
218 }
219
220 @Override
221 public Object onRetainNonConfigurationInstance() {
222 TrackListAdapter a = mAdapter;
223 mAdapterSent = true;
224 return a;
225 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800226
The Android Open Source Project792a2202009-03-03 19:32:30 -0800227 @Override
228 public void onDestroy() {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800229 ListView lv = getListView();
Marco Nelissend7d9ba52010-02-12 14:43:25 -0800230 if (lv != null) {
231 if (mUseLastListPos) {
232 mLastListPosCourse = lv.getFirstVisiblePosition();
233 View cv = lv.getChildAt(0);
234 if (cv != null) {
235 mLastListPosFine = cv.getTop();
236 }
237 }
238 if (mEditMode) {
239 // clear the listeners so we won't get any more callbacks
240 ((TouchInterceptor) lv).setDropListener(null);
241 ((TouchInterceptor) lv).setRemoveListener(null);
Marco Nelissen23b531e2009-12-12 14:07:59 -0800242 }
Marco Nelissen9e0f1fd2009-12-12 13:41:34 -0800243 }
Marco Nelissend7d9ba52010-02-12 14:43:25 -0800244
Marco Nelissenf33a5752010-01-27 15:35:43 -0800245 MusicUtils.unbindFromService(mToken);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800246 try {
247 if ("nowplaying".equals(mPlaylist)) {
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700248 unregisterReceiverSafe(mNowPlayingListener);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800249 } else {
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700250 unregisterReceiverSafe(mTrackListListener);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800251 }
252 } catch (IllegalArgumentException ex) {
253 // we end up here in case we never registered the listeners
254 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800255
Marco Nelissen0e175782009-12-03 14:24:53 -0800256 // If we have an adapter and didn't send it off to another activity yet, we should
257 // close its cursor, which we do by assigning a null cursor to it. Doing this
258 // instead of closing the cursor directly keeps the framework from accessing
259 // the closed cursor later.
Marco Nelissen8e732ff2009-10-12 12:29:09 -0700260 if (!mAdapterSent && mAdapter != null) {
Marco Nelissen0e175782009-12-03 14:24:53 -0800261 mAdapter.changeCursor(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800262 }
Marco Nelissen0164ebf2009-08-13 09:58:43 -0700263 // Because we pass the adapter to the next activity, we need to make
264 // sure it doesn't keep a reference to this activity. We can do this
265 // by clearing its DatasetObservers, which setListAdapter(null) does.
266 setListAdapter(null);
267 mAdapter = null;
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700268 unregisterReceiverSafe(mScanListener);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800269 super.onDestroy();
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700270 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800271
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700272 /**
273 * Unregister a receiver, but eat the exception that is thrown if the
274 * receiver was never registered to begin with. This is a little easier
275 * than keeping track of whether the receivers have actually been
276 * registered by the time onDestroy() is called.
277 */
278 private void unregisterReceiverSafe(BroadcastReceiver receiver) {
279 try {
280 unregisterReceiver(receiver);
281 } catch (IllegalArgumentException e) {
282 // ignore
283 }
284 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800285
The Android Open Source Project792a2202009-03-03 19:32:30 -0800286 @Override
287 public void onResume() {
288 super.onResume();
289 if (mTrackCursor != null) {
290 getListView().invalidateViews();
291 }
292 MusicUtils.setSpinnerState(this);
293 }
294 @Override
295 public void onPause() {
296 mReScanHandler.removeCallbacksAndMessages(null);
297 super.onPause();
298 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800299
The Android Open Source Project792a2202009-03-03 19:32:30 -0800300 /*
301 * This listener gets called when the media scanner starts up or finishes, and
302 * when the sd card is unmounted.
303 */
304 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
305 @Override
306 public void onReceive(Context context, Intent intent) {
307 String action = intent.getAction();
Jack Heb0fba8b2017-01-26 15:54:38 -0800308 if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action)
309 || Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800310 MusicUtils.setSpinnerState(TrackBrowserActivity.this);
311 }
312 mReScanHandler.sendEmptyMessage(0);
313 }
314 };
Jack Heb0fba8b2017-01-26 15:54:38 -0800315
The Android Open Source Project792a2202009-03-03 19:32:30 -0800316 private Handler mReScanHandler = new Handler() {
317 @Override
318 public void handleMessage(Message msg) {
Marco Nelissen42bcc212009-09-01 13:22:19 -0700319 if (mAdapter != null) {
320 getTrackCursor(mAdapter.getQueryHandler(), null, true);
321 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800322 // if the query results in a null cursor, onQueryComplete() will
323 // call init(), which will post a delayed message to this handler
324 // in order to try again.
325 }
326 };
Jack Heb0fba8b2017-01-26 15:54:38 -0800327
The Android Open Source Project792a2202009-03-03 19:32:30 -0800328 public void onSaveInstanceState(Bundle outcicle) {
329 // need to store the selected item so we don't lose it in case
330 // of an orientation switch. Otherwise we could lose it while
331 // in the middle of specifying a playlist to add the item to.
332 outcicle.putLong("selectedtrack", mSelectedId);
333 outcicle.putString("artist", mArtistId);
334 outcicle.putString("album", mAlbumId);
335 outcicle.putString("playlist", mPlaylist);
336 outcicle.putString("genre", mGenre);
337 outcicle.putBoolean("editmode", mEditMode);
338 super.onSaveInstanceState(outcicle);
339 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800340
Jack Heb0fba8b2017-01-26 15:54:38 -0800341 public void init(Cursor newCursor, boolean isLimited) {
Marco Nelissen42bcc212009-09-01 13:22:19 -0700342 if (mAdapter == null) {
343 return;
344 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800345 mAdapter.changeCursor(newCursor); // also sets mTrackCursor
Jack Heb0fba8b2017-01-26 15:54:38 -0800346
The Android Open Source Project792a2202009-03-03 19:32:30 -0800347 if (mTrackCursor == null) {
348 MusicUtils.displayDatabaseError(this);
349 closeContextMenu();
350 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
351 return;
352 }
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800353
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800354 MusicUtils.hideDatabaseError(this);
355 mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
356 setTitle();
357
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800358 // Restore previous position
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800359 if (mLastListPosCourse >= 0 && mUseLastListPos) {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800360 ListView lv = getListView();
361 // this hack is needed because otherwise the position doesn't change
362 // for the 2nd (non-limited) cursor
363 lv.setAdapter(lv.getAdapter());
364 lv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
365 if (!isLimited) {
366 mLastListPosCourse = -1;
367 }
368 }
369
The Android Open Source Project792a2202009-03-03 19:32:30 -0800370 // When showing the queue, position the selection on the currently playing track
371 // Otherwise, position the selection on the first matching artist, if any
372 IntentFilter f = new IntentFilter();
373 f.addAction(MediaPlaybackService.META_CHANGED);
374 f.addAction(MediaPlaybackService.QUEUE_CHANGED);
375 if ("nowplaying".equals(mPlaylist)) {
376 try {
377 int cur = MusicUtils.sService.getQueuePosition();
378 setSelection(cur);
379 registerReceiver(mNowPlayingListener, new IntentFilter(f));
380 mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
381 } catch (RemoteException ex) {
382 }
383 } else {
384 String key = getIntent().getStringExtra("artist");
385 if (key != null) {
386 int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID);
387 mTrackCursor.moveToFirst();
Jack Heb0fba8b2017-01-26 15:54:38 -0800388 while (!mTrackCursor.isAfterLast()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800389 String artist = mTrackCursor.getString(keyidx);
390 if (artist.equals(key)) {
391 setSelection(mTrackCursor.getPosition());
392 break;
393 }
394 mTrackCursor.moveToNext();
395 }
396 }
397 registerReceiver(mTrackListListener, new IntentFilter(f));
398 mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
399 }
400 }
401
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800402 private void setAlbumArtBackground() {
Marco Nelissen355e1342010-07-30 17:34:03 -0700403 if (!mEditMode) {
404 try {
405 long albumid = Long.valueOf(mAlbumId);
406 Bitmap bm = MusicUtils.getArtwork(TrackBrowserActivity.this, -1, albumid, false);
407 if (bm != null) {
408 MusicUtils.setBackground(mTrackList, bm);
409 mTrackList.setCacheColorHint(0);
410 return;
411 }
412 } catch (Exception ex) {
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800413 }
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800414 }
Marco Nelissen355e1342010-07-30 17:34:03 -0700415 mTrackList.setBackgroundColor(0xff000000);
416 mTrackList.setCacheColorHint(0);
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800417 }
418
The Android Open Source Project792a2202009-03-03 19:32:30 -0800419 private void setTitle() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800420 CharSequence fancyName = null;
421 if (mAlbumId != null) {
422 int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0;
423 if (numresults > 0) {
424 mTrackCursor.moveToFirst();
425 int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
426 fancyName = mTrackCursor.getString(idx);
427 // For compilation albums show only the album title,
428 // but for regular albums show "artist - album".
429 // To determine whether something is a compilation
430 // album, do a query for the artist + album of the
431 // first item, and see if it returns the same number
432 // of results as the album query.
Jack Heb0fba8b2017-01-26 15:54:38 -0800433 String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId + "' AND "
434 + MediaStore.Audio.Media.ARTIST_ID + "="
435 + mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow(
436 MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800437 Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
Jack Heb0fba8b2017-01-26 15:54:38 -0800438 new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800439 if (cursor != null) {
440 if (cursor.getCount() != numresults) {
441 // compilation album
442 fancyName = mTrackCursor.getString(idx);
Jack Heb0fba8b2017-01-26 15:54:38 -0800443 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800444 cursor.deactivate();
445 }
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800446 if (fancyName == null || fancyName.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800447 fancyName = getString(R.string.unknown_album_name);
448 }
449 }
450 } else if (mPlaylist != null) {
451 if (mPlaylist.equals("nowplaying")) {
452 if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) {
453 fancyName = getText(R.string.partyshuffle_title);
454 } else {
455 fancyName = getText(R.string.nowplaying_title);
456 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800457 } else if (mPlaylist.equals("podcasts")) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800458 fancyName = getText(R.string.podcasts_title);
Jack Heb0fba8b2017-01-26 15:54:38 -0800459 } else if (mPlaylist.equals("recentlyadded")) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800460 fancyName = getText(R.string.recentlyadded_title);
461 } else {
Jack Heb0fba8b2017-01-26 15:54:38 -0800462 String[] cols = new String[] {MediaStore.Audio.Playlists.NAME};
The Android Open Source Project792a2202009-03-03 19:32:30 -0800463 Cursor cursor = MusicUtils.query(this,
Jack Heb0fba8b2017-01-26 15:54:38 -0800464 ContentUris.withAppendedId(
465 Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)),
The Android Open Source Project792a2202009-03-03 19:32:30 -0800466 cols, null, null, null);
467 if (cursor != null) {
468 if (cursor.getCount() != 0) {
469 cursor.moveToFirst();
470 fancyName = cursor.getString(0);
471 }
472 cursor.deactivate();
473 }
474 }
475 } else if (mGenre != null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800476 String[] cols = new String[] {MediaStore.Audio.Genres.NAME};
The Android Open Source Project792a2202009-03-03 19:32:30 -0800477 Cursor cursor = MusicUtils.query(this,
Jack Heb0fba8b2017-01-26 15:54:38 -0800478 ContentUris.withAppendedId(
479 MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)),
The Android Open Source Project792a2202009-03-03 19:32:30 -0800480 cols, null, null, null);
481 if (cursor != null) {
482 if (cursor.getCount() != 0) {
483 cursor.moveToFirst();
484 fancyName = cursor.getString(0);
485 }
486 cursor.deactivate();
487 }
488 }
489
490 if (fancyName != null) {
491 setTitle(fancyName);
492 } else {
493 setTitle(R.string.tracks_title);
494 }
495 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800496
497 private TouchInterceptor.DropListener mDropListener = new TouchInterceptor.DropListener() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800498 public void drop(int from, int to) {
499 if (mTrackCursor instanceof NowPlayingCursor) {
500 // update the currently playing list
501 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
502 c.moveItem(from, to);
Jack Heb0fba8b2017-01-26 15:54:38 -0800503 ((TrackListAdapter) getListAdapter()).notifyDataSetChanged();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800504 getListView().invalidateViews();
505 mDeletedOneRow = true;
506 } else {
507 // update a saved playlist
Jack Heb0fba8b2017-01-26 15:54:38 -0800508 MediaStore.Audio.Playlists.Members.moveItem(
509 getContentResolver(), Long.valueOf(mPlaylist), from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800510 }
511 }
512 };
Jack Heb0fba8b2017-01-26 15:54:38 -0800513
The Android Open Source Project792a2202009-03-03 19:32:30 -0800514 private TouchInterceptor.RemoveListener mRemoveListener =
Jack Heb0fba8b2017-01-26 15:54:38 -0800515 new TouchInterceptor.RemoveListener() {
516 public void remove(int which) {
517 removePlaylistItem(which);
518 }
519 };
The Android Open Source Project792a2202009-03-03 19:32:30 -0800520
521 private void removePlaylistItem(int which) {
522 View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
Marco Nelissen42131382009-12-04 09:40:22 -0800523 if (v == null) {
524 Log.d(LOGTAG, "No view when removing playlist item " + which);
525 return;
526 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800527 try {
Jack Heb0fba8b2017-01-26 15:54:38 -0800528 if (MusicUtils.sService != null && which != MusicUtils.sService.getQueuePosition()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800529 mDeletedOneRow = true;
530 }
531 } catch (RemoteException e) {
532 // Service died, so nothing playing.
533 mDeletedOneRow = true;
534 }
535 v.setVisibility(View.GONE);
536 mTrackList.invalidateViews();
537 if (mTrackCursor instanceof NowPlayingCursor) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800538 ((NowPlayingCursor) mTrackCursor).removeItem(which);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800539 } else {
Jack Heb0fba8b2017-01-26 15:54:38 -0800540 int colidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members._ID);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800541 mTrackCursor.moveToPosition(which);
542 long id = mTrackCursor.getLong(colidx);
Jack Heb0fba8b2017-01-26 15:54:38 -0800543 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
544 "external", Long.valueOf(mPlaylist));
545 getContentResolver().delete(ContentUris.withAppendedId(uri, id), null, null);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800546 }
547 v.setVisibility(View.VISIBLE);
548 mTrackList.invalidateViews();
549 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800550
The Android Open Source Project792a2202009-03-03 19:32:30 -0800551 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
552 @Override
553 public void onReceive(Context context, Intent intent) {
554 getListView().invalidateViews();
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800555 if (!mEditMode) {
556 MusicUtils.updateNowPlaying(TrackBrowserActivity.this);
557 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800558 }
559 };
560
561 private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() {
562 @Override
563 public void onReceive(Context context, Intent intent) {
564 if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) {
565 getListView().invalidateViews();
566 } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) {
567 if (mDeletedOneRow) {
568 // This is the notification for a single row that was
569 // deleted previously, which is already reflected in
570 // the UI.
571 mDeletedOneRow = false;
572 return;
573 }
Marco Nelissen04b29c92010-02-11 17:49:53 -0800574 // The service could disappear while the broadcast was in flight,
575 // so check to see if it's still valid
576 if (MusicUtils.sService == null) {
577 finish();
578 return;
579 }
Marco Nelissena51d6fe2010-08-31 15:24:18 -0700580 if (mAdapter != null) {
581 Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
582 if (c.getCount() == 0) {
583 finish();
584 return;
585 }
586 mAdapter.changeCursor(c);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800587 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800588 }
589 }
590 };
591
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700592 // Cursor should be positioned on the entry to be checked
593 // Returns false if the entry matches the naming pattern used for recordings,
594 // or if it is marked as not music in the database.
595 private boolean isMusic(Cursor c) {
596 int titleidx = c.getColumnIndex(MediaStore.Audio.Media.TITLE);
597 int albumidx = c.getColumnIndex(MediaStore.Audio.Media.ALBUM);
598 int artistidx = c.getColumnIndex(MediaStore.Audio.Media.ARTIST);
599
600 String title = c.getString(titleidx);
601 String album = c.getString(albumidx);
602 String artist = c.getString(artistidx);
Jack Heb0fba8b2017-01-26 15:54:38 -0800603 if (MediaStore.UNKNOWN_STRING.equals(album) && MediaStore.UNKNOWN_STRING.equals(artist)
604 && title != null && title.startsWith("recording")) {
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700605 // not music
606 return false;
607 }
608
609 int ismusic_idx = c.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC);
610 boolean ismusic = true;
611 if (ismusic_idx >= 0) {
612 ismusic = mTrackCursor.getInt(ismusic_idx) != 0;
613 }
614 return ismusic;
615 }
616
The Android Open Source Project792a2202009-03-03 19:32:30 -0800617 @Override
618 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
619 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
620 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
621 MusicUtils.makePlaylistMenu(this, sub);
622 if (mEditMode) {
623 menu.add(0, REMOVE, 0, R.string.remove_from_playlist);
624 }
625 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu);
626 menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800627 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
Jack Heb0fba8b2017-01-26 15:54:38 -0800628 mSelectedPosition = mi.position;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800629 mTrackCursor.moveToPosition(mSelectedPosition);
630 try {
Jack Heb0fba8b2017-01-26 15:54:38 -0800631 int id_idx =
632 mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700633 mSelectedId = mTrackCursor.getLong(id_idx);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800634 } catch (IllegalArgumentException ex) {
635 mSelectedId = mi.id;
636 }
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700637 // only add the 'search' menu if the selected item is music
638 if (isMusic(mTrackCursor)) {
639 menu.add(0, SEARCH, 0, R.string.search_title);
640 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800641 mCurrentAlbumName = mTrackCursor.getString(
642 mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
643 mCurrentArtistNameForAlbum = mTrackCursor.getString(
644 mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
645 mCurrentTrackName = mTrackCursor.getString(
646 mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800647 menu.setHeaderTitle(mCurrentTrackName);
648 }
649
650 @Override
651 public boolean onContextItemSelected(MenuItem item) {
652 switch (item.getItemId()) {
653 case PLAY_SELECTION: {
654 // play the track
655 int position = mSelectedPosition;
656 MusicUtils.playAll(this, mTrackCursor, position);
657 return true;
658 }
659
660 case QUEUE: {
Jack Heb0fba8b2017-01-26 15:54:38 -0800661 long[] list = new long[] {mSelectedId};
The Android Open Source Project792a2202009-03-03 19:32:30 -0800662 MusicUtils.addToCurrentPlaylist(this, list);
663 return true;
664 }
665
666 case NEW_PLAYLIST: {
667 Intent intent = new Intent();
668 intent.setClass(this, CreatePlaylist.class);
669 startActivityForResult(intent, NEW_PLAYLIST);
670 return true;
671 }
672
673 case PLAYLIST_SELECTED: {
Jack Heb0fba8b2017-01-26 15:54:38 -0800674 long[] list = new long[] {mSelectedId};
Marco Nelissenbd447b62009-06-29 14:52:05 -0700675 long playlist = item.getIntent().getLongExtra("playlist", 0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800676 MusicUtils.addToPlaylist(this, list, playlist);
677 return true;
678 }
679
680 case USE_AS_RINGTONE:
681 // Set the system setting to make this the current ringtone
682 MusicUtils.setRingtone(this, mSelectedId);
683 return true;
684
685 case DELETE_ITEM: {
Jack Heb0fba8b2017-01-26 15:54:38 -0800686 long[] list = new long[1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800687 list[0] = (int) mSelectedId;
688 Bundle b = new Bundle();
Eric Fischerecf5b652010-10-18 14:20:30 -0700689 String f;
690 if (android.os.Environment.isExternalStorageRemovable()) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800691 f = getString(R.string.delete_song_desc);
Eric Fischerecf5b652010-10-18 14:20:30 -0700692 } else {
Jack Heb0fba8b2017-01-26 15:54:38 -0800693 f = getString(R.string.delete_song_desc_nosdcard);
Eric Fischerecf5b652010-10-18 14:20:30 -0700694 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800695 String desc = String.format(f, mCurrentTrackName);
696 b.putString("description", desc);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700697 b.putLongArray("items", list);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800698 Intent intent = new Intent();
699 intent.setClass(this, DeleteItems.class);
700 intent.putExtras(b);
701 startActivityForResult(intent, -1);
702 return true;
703 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800704
The Android Open Source Project792a2202009-03-03 19:32:30 -0800705 case REMOVE:
706 removePlaylistItem(mSelectedPosition);
707 return true;
Jack Heb0fba8b2017-01-26 15:54:38 -0800708
The Android Open Source Project792a2202009-03-03 19:32:30 -0800709 case SEARCH:
710 doSearch();
711 return true;
712 }
713 return super.onContextItemSelected(item);
714 }
715
716 void doSearch() {
717 CharSequence title = null;
718 String query = null;
Jack Heb0fba8b2017-01-26 15:54:38 -0800719
The Android Open Source Project792a2202009-03-03 19:32:30 -0800720 Intent i = new Intent();
721 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
Marco Nelissen4341b502009-05-06 14:57:37 -0700722 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Jack Heb0fba8b2017-01-26 15:54:38 -0800723
Marco Nelissen9882f542009-08-14 10:06:15 -0700724 title = mCurrentTrackName;
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800725 if (MediaStore.UNKNOWN_STRING.equals(mCurrentArtistNameForAlbum)) {
Marco Nelissen9882f542009-08-14 10:06:15 -0700726 query = mCurrentTrackName;
727 } else {
728 query = mCurrentArtistNameForAlbum + " " + mCurrentTrackName;
729 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
730 }
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800731 if (MediaStore.UNKNOWN_STRING.equals(mCurrentAlbumName)) {
Marco Nelissen9882f542009-08-14 10:06:15 -0700732 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
733 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800734 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*");
735 title = getString(R.string.mediasearch, title);
736 i.putExtra(SearchManager.QUERY, query);
737
738 startActivity(Intent.createChooser(i, title));
739 }
740
741 // In order to use alt-up/down as a shortcut for moving the selected item
742 // in the list, we need to override dispatchKeyEvent, not onKeyDown.
743 // (onKeyDown never sees these events, since they are handled by the list)
744 @Override
745 public boolean dispatchKeyEvent(KeyEvent event) {
b35941f8dcd2012-05-18 16:30:23 +0800746 int curpos = mTrackList.getSelectedItemPosition();
Jack Heb0fba8b2017-01-26 15:54:38 -0800747 if (mPlaylist != null && !mPlaylist.equals("recentlyadded") && curpos >= 0
748 && event.getMetaState() != 0 && event.getAction() == KeyEvent.ACTION_DOWN) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800749 switch (event.getKeyCode()) {
750 case KeyEvent.KEYCODE_DPAD_UP:
751 moveItem(true);
752 return true;
753 case KeyEvent.KEYCODE_DPAD_DOWN:
754 moveItem(false);
755 return true;
756 case KeyEvent.KEYCODE_DEL:
757 removeItem();
758 return true;
759 }
760 }
761
762 return super.dispatchKeyEvent(event);
763 }
764
765 private void removeItem() {
766 int curcount = mTrackCursor.getCount();
767 int curpos = mTrackList.getSelectedItemPosition();
768 if (curcount == 0 || curpos < 0) {
769 return;
770 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800771
The Android Open Source Project792a2202009-03-03 19:32:30 -0800772 if ("nowplaying".equals(mPlaylist)) {
773 // remove track from queue
774
775 // Work around bug 902971. To get quick visual feedback
776 // of the deletion of the item, hide the selected view.
777 try {
778 if (curpos != MusicUtils.sService.getQueuePosition()) {
779 mDeletedOneRow = true;
780 }
781 } catch (RemoteException ex) {
782 }
783 View v = mTrackList.getSelectedView();
784 v.setVisibility(View.GONE);
785 mTrackList.invalidateViews();
Jack Heb0fba8b2017-01-26 15:54:38 -0800786 ((NowPlayingCursor) mTrackCursor).removeItem(curpos);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800787 v.setVisibility(View.VISIBLE);
788 mTrackList.invalidateViews();
789 } else {
790 // remove track from playlist
Jack Heb0fba8b2017-01-26 15:54:38 -0800791 int colidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members._ID);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800792 mTrackCursor.moveToPosition(curpos);
793 long id = mTrackCursor.getLong(colidx);
Jack Heb0fba8b2017-01-26 15:54:38 -0800794 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
795 "external", Long.valueOf(mPlaylist));
796 getContentResolver().delete(ContentUris.withAppendedId(uri, id), null, null);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800797 curcount--;
798 if (curcount == 0) {
799 finish();
800 } else {
801 mTrackList.setSelection(curpos < curcount ? curpos : curcount);
802 }
803 }
804 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800805
The Android Open Source Project792a2202009-03-03 19:32:30 -0800806 private void moveItem(boolean up) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800807 int curcount = mTrackCursor.getCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800808 int curpos = mTrackList.getSelectedItemPosition();
Jack Heb0fba8b2017-01-26 15:54:38 -0800809 if ((up && curpos < 1) || (!up && curpos >= curcount - 1)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800810 return;
811 }
812
813 if (mTrackCursor instanceof NowPlayingCursor) {
814 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
815 c.moveItem(curpos, up ? curpos - 1 : curpos + 1);
Jack Heb0fba8b2017-01-26 15:54:38 -0800816 ((TrackListAdapter) getListAdapter()).notifyDataSetChanged();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800817 getListView().invalidateViews();
818 mDeletedOneRow = true;
819 if (up) {
820 mTrackList.setSelection(curpos - 1);
821 } else {
822 mTrackList.setSelection(curpos + 1);
823 }
824 } else {
825 int colidx = mTrackCursor.getColumnIndexOrThrow(
826 MediaStore.Audio.Playlists.Members.PLAY_ORDER);
827 mTrackCursor.moveToPosition(curpos);
828 int currentplayidx = mTrackCursor.getInt(colidx);
Jack Heb0fba8b2017-01-26 15:54:38 -0800829 Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri(
830 "external", Long.valueOf(mPlaylist));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800831 ContentValues values = new ContentValues();
832 String where = MediaStore.Audio.Playlists.Members._ID + "=?";
Jack Heb0fba8b2017-01-26 15:54:38 -0800833 String[] wherearg = new String[1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800834 ContentResolver res = getContentResolver();
835 if (up) {
836 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1);
837 wherearg[0] = mTrackCursor.getString(0);
838 res.update(baseUri, values, where, wherearg);
839 mTrackCursor.moveToPrevious();
840 } else {
841 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1);
842 wherearg[0] = mTrackCursor.getString(0);
843 res.update(baseUri, values, where, wherearg);
844 mTrackCursor.moveToNext();
845 }
846 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx);
847 wherearg[0] = mTrackCursor.getString(0);
848 res.update(baseUri, values, where, wherearg);
849 }
850 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800851
The Android Open Source Project792a2202009-03-03 19:32:30 -0800852 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -0800853 protected void onListItemClick(ListView l, View v, int position, long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800854 if (mTrackCursor.getCount() == 0) {
855 return;
856 }
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800857 // When selecting a track from the queue, just jump there instead of
858 // reloading the queue. This is both faster, and prevents accidentally
859 // dropping out of party shuffle.
860 if (mTrackCursor instanceof NowPlayingCursor) {
861 if (MusicUtils.sService != null) {
862 try {
863 MusicUtils.sService.setQueuePosition(position);
864 return;
865 } catch (RemoteException ex) {
866 }
867 }
868 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800869 MusicUtils.playAll(this, mTrackCursor, position);
870 }
871
872 @Override
873 public boolean onCreateOptionsMenu(Menu menu) {
874 /* This activity is used for a number of different browsing modes, and the menu can
875 * be different for each of them:
876 * - all tracks, optionally restricted to an album, artist or playlist
877 * - the list of currently playing songs
878 */
879 super.onCreateOptionsMenu(menu);
880 if (mPlaylist == null) {
Marco Nelissen3d4b2622010-01-06 12:46:49 -0800881 menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(R.drawable.ic_menu_play_clip);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800882 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800883 menu.add(0, PARTY_SHUFFLE, 0,
884 R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
The Android Open Source Project792a2202009-03-03 19:32:30 -0800885 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
886 if (mPlaylist != null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800887 menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist)
888 .setIcon(android.R.drawable.ic_menu_save);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800889 if (mPlaylist.equals("nowplaying")) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800890 menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist)
891 .setIcon(R.drawable.ic_menu_clear_playlist);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800892 }
893 }
894 return true;
895 }
896
897 @Override
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800898 public boolean onPrepareOptionsMenu(Menu menu) {
899 MusicUtils.setPartyShuffleMenuIcon(menu);
900 return super.onPrepareOptionsMenu(menu);
901 }
902
903 @Override
The Android Open Source Project792a2202009-03-03 19:32:30 -0800904 public boolean onOptionsItemSelected(MenuItem item) {
905 Intent intent;
906 Cursor cursor;
907 switch (item.getItemId()) {
908 case PLAY_ALL: {
909 MusicUtils.playAll(this, mTrackCursor);
910 return true;
911 }
912
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800913 case PARTY_SHUFFLE:
914 MusicUtils.togglePartyShuffle();
915 break;
Jack Heb0fba8b2017-01-26 15:54:38 -0800916
The Android Open Source Project792a2202009-03-03 19:32:30 -0800917 case SHUFFLE_ALL:
918 // Should 'shuffle all' shuffle ALL, or only the tracks shown?
919 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
Jack Heb0fba8b2017-01-26 15:54:38 -0800920 new String[] {MediaStore.Audio.Media._ID},
The Android Open Source Project792a2202009-03-03 19:32:30 -0800921 MediaStore.Audio.Media.IS_MUSIC + "=1", null,
922 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
923 if (cursor != null) {
924 MusicUtils.shuffleAll(this, cursor);
925 cursor.close();
926 }
927 return true;
Jack Heb0fba8b2017-01-26 15:54:38 -0800928
The Android Open Source Project792a2202009-03-03 19:32:30 -0800929 case SAVE_AS_PLAYLIST:
930 intent = new Intent();
931 intent.setClass(this, CreatePlaylist.class);
932 startActivityForResult(intent, SAVE_AS_PLAYLIST);
933 return true;
Jack Heb0fba8b2017-01-26 15:54:38 -0800934
The Android Open Source Project792a2202009-03-03 19:32:30 -0800935 case CLEAR_PLAYLIST:
936 // We only clear the current playlist
937 MusicUtils.clearQueue();
938 return true;
939 }
940 return super.onOptionsItemSelected(item);
941 }
942
943 @Override
944 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
945 switch (requestCode) {
946 case SCAN_DONE:
947 if (resultCode == RESULT_CANCELED) {
948 finish();
949 } else {
Marco Nelissen4248ed22009-07-30 08:28:49 -0700950 getTrackCursor(mAdapter.getQueryHandler(), null, true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800951 }
952 break;
Jack Heb0fba8b2017-01-26 15:54:38 -0800953
The Android Open Source Project792a2202009-03-03 19:32:30 -0800954 case NEW_PLAYLIST:
955 if (resultCode == RESULT_OK) {
956 Uri uri = intent.getData();
957 if (uri != null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800958 long[] list = new long[] {mSelectedId};
959 MusicUtils.addToPlaylist(
960 this, list, Integer.valueOf(uri.getLastPathSegment()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800961 }
962 }
963 break;
964
965 case SAVE_AS_PLAYLIST:
966 if (resultCode == RESULT_OK) {
967 Uri uri = intent.getData();
968 if (uri != null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800969 long[] list = MusicUtils.getSongListForCursor(mTrackCursor);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800970 int plid = Integer.parseInt(uri.getLastPathSegment());
971 MusicUtils.addToPlaylist(this, list, plid);
972 }
973 }
974 break;
975 }
976 }
Marco Nelissen4248ed22009-07-30 08:28:49 -0700977
Jack Heb0fba8b2017-01-26 15:54:38 -0800978 private Cursor getTrackCursor(
979 TrackListAdapter.TrackQueryHandler queryhandler, String filter, boolean async) {
Marco Nelissen4248ed22009-07-30 08:28:49 -0700980 if (queryhandler == null) {
981 throw new IllegalArgumentException();
982 }
983
The Android Open Source Project792a2202009-03-03 19:32:30 -0800984 Cursor ret = null;
985 mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
986 StringBuilder where = new StringBuilder();
987 where.append(MediaStore.Audio.Media.TITLE + " != ''");
Marco Nelissen4248ed22009-07-30 08:28:49 -0700988
The Android Open Source Project792a2202009-03-03 19:32:30 -0800989 if (mGenre != null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800990 Uri uri = MediaStore.Audio.Genres.Members.getContentUri(
991 "external", Integer.valueOf(mGenre));
Marco Nelissen89f6c662010-09-02 15:20:23 -0700992 if (!TextUtils.isEmpty(filter)) {
993 uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
994 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800995 mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
Jack Heb0fba8b2017-01-26 15:54:38 -0800996 ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null, mSortOrder, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800997 } else if (mPlaylist != null) {
998 if (mPlaylist.equals("nowplaying")) {
999 if (MusicUtils.sService != null) {
1000 ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
1001 if (ret.getCount() == 0) {
1002 finish();
1003 }
1004 } else {
1005 // Nothing is playing.
1006 }
1007 } else if (mPlaylist.equals("podcasts")) {
1008 where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1");
Marco Nelissen89f6c662010-09-02 15:20:23 -07001009 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
1010 if (!TextUtils.isEmpty(filter)) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001011 uri = uri.buildUpon()
1012 .appendQueryParameter("filter", Uri.encode(filter))
1013 .build();
Marco Nelissen89f6c662010-09-02 15:20:23 -07001014 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001015 ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null,
Marco Nelissen4248ed22009-07-30 08:28:49 -07001016 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001017 } else if (mPlaylist.equals("recentlyadded")) {
1018 // do a query for all songs added in the last X weeks
Marco Nelissen89f6c662010-09-02 15:20:23 -07001019 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
1020 if (!TextUtils.isEmpty(filter)) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001021 uri = uri.buildUpon()
1022 .appendQueryParameter("filter", Uri.encode(filter))
1023 .build();
Marco Nelissen89f6c662010-09-02 15:20:23 -07001024 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001025 int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
1026 where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">");
1027 where.append(System.currentTimeMillis() / 1000 - X);
Jack Heb0fba8b2017-01-26 15:54:38 -08001028 ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null,
Marco Nelissen4248ed22009-07-30 08:28:49 -07001029 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001030 } else {
Jack Heb0fba8b2017-01-26 15:54:38 -08001031 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
1032 "external", Long.valueOf(mPlaylist));
Marco Nelissen89f6c662010-09-02 15:20:23 -07001033 if (!TextUtils.isEmpty(filter)) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001034 uri = uri.buildUpon()
1035 .appendQueryParameter("filter", Uri.encode(filter))
1036 .build();
Marco Nelissen89f6c662010-09-02 15:20:23 -07001037 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001038 mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
Jack Heb0fba8b2017-01-26 15:54:38 -08001039 ret = queryhandler.doQuery(
1040 uri, mPlaylistMemberCols, where.toString(), null, mSortOrder, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001041 }
1042 } else {
1043 if (mAlbumId != null) {
1044 where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId);
1045 mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder;
1046 }
1047 if (mArtistId != null) {
1048 where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId);
1049 }
1050 where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
Marco Nelissen89f6c662010-09-02 15:20:23 -07001051 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
1052 if (!TextUtils.isEmpty(filter)) {
1053 uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
1054 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001055 ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null, mSortOrder, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001056 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001057
The Android Open Source Project792a2202009-03-03 19:32:30 -08001058 // This special case is for the "nowplaying" cursor, which cannot be handled
1059 // asynchronously using AsyncQueryHandler, so we do some extra initialization here.
Marco Nelissen4248ed22009-07-30 08:28:49 -07001060 if (ret != null && async) {
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001061 init(ret, false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001062 setTitle();
1063 }
1064 return ret;
1065 }
1066
Jack Heb0fba8b2017-01-26 15:54:38 -08001067 private class NowPlayingCursor extends AbstractCursor {
1068 public NowPlayingCursor(IMediaPlaybackService service, String[] cols) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001069 mCols = cols;
Jack Heb0fba8b2017-01-26 15:54:38 -08001070 mService = service;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001071 makeNowPlayingCursor();
1072 }
1073 private void makeNowPlayingCursor() {
1074 mCurrentPlaylistCursor = null;
1075 try {
1076 mNowPlaying = mService.getQueue();
1077 } catch (RemoteException ex) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001078 mNowPlaying = new long[0];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001079 }
1080 mSize = mNowPlaying.length;
1081 if (mSize == 0) {
1082 return;
1083 }
1084
1085 StringBuilder where = new StringBuilder();
1086 where.append(MediaStore.Audio.Media._ID + " IN (");
1087 for (int i = 0; i < mSize; i++) {
1088 where.append(mNowPlaying[i]);
1089 if (i < mSize - 1) {
1090 where.append(",");
1091 }
1092 }
1093 where.append(")");
1094
1095 mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this,
Jack Heb0fba8b2017-01-26 15:54:38 -08001096 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCols, where.toString(), null,
1097 MediaStore.Audio.Media._ID);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001098
1099 if (mCurrentPlaylistCursor == null) {
1100 mSize = 0;
1101 return;
1102 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001103
The Android Open Source Project792a2202009-03-03 19:32:30 -08001104 int size = mCurrentPlaylistCursor.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001105 mCursorIdxs = new long[size];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001106 mCurrentPlaylistCursor.moveToFirst();
1107 int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
1108 for (int i = 0; i < size; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001109 mCursorIdxs[i] = mCurrentPlaylistCursor.getLong(colidx);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001110 mCurrentPlaylistCursor.moveToNext();
1111 }
1112 mCurrentPlaylistCursor.moveToFirst();
1113 mCurPos = -1;
Jack Heb0fba8b2017-01-26 15:54:38 -08001114
The Android Open Source Project792a2202009-03-03 19:32:30 -08001115 // At this point we can verify the 'now playing' list we got
1116 // earlier to make sure that all the items in there still exist
1117 // in the database, and remove those that aren't. This way we
1118 // don't get any blank items in the list.
1119 try {
1120 int removed = 0;
1121 for (int i = mNowPlaying.length - 1; i >= 0; i--) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001122 long trackid = mNowPlaying[i];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001123 int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);
1124 if (crsridx < 0) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001125 // Log.i("@@@@@", "item no longer exists in db: " + trackid);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001126 removed += mService.removeTrack(trackid);
1127 }
1128 }
1129 if (removed > 0) {
1130 mNowPlaying = mService.getQueue();
1131 mSize = mNowPlaying.length;
1132 if (mSize == 0) {
1133 mCursorIdxs = null;
1134 return;
1135 }
1136 }
1137 } catch (RemoteException ex) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001138 mNowPlaying = new long[0];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001139 }
1140 }
1141
1142 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001143 public int getCount() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001144 return mSize;
1145 }
1146
1147 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001148 public boolean onMove(int oldPosition, int newPosition) {
1149 if (oldPosition == newPosition) return true;
1150
Marco Nelissenea936712010-02-01 17:01:22 -08001151 if (mNowPlaying == null || mCursorIdxs == null || newPosition >= mNowPlaying.length) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001152 return false;
1153 }
1154
1155 // The cursor doesn't have any duplicates in it, and is not ordered
1156 // in queue-order, so we need to figure out where in the cursor we
1157 // should be.
Jack Heb0fba8b2017-01-26 15:54:38 -08001158
Marco Nelissenbd447b62009-06-29 14:52:05 -07001159 long newid = mNowPlaying[newPosition];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001160 int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
1161 mCurrentPlaylistCursor.moveToPosition(crsridx);
1162 mCurPos = newPosition;
Jack Heb0fba8b2017-01-26 15:54:38 -08001163
The Android Open Source Project792a2202009-03-03 19:32:30 -08001164 return true;
1165 }
1166
Jack Heb0fba8b2017-01-26 15:54:38 -08001167 public boolean removeItem(int which) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001168 try {
1169 if (mService.removeTracks(which, which) == 0) {
1170 return false; // delete failed
1171 }
1172 int i = (int) which;
1173 mSize--;
1174 while (i < mSize) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001175 mNowPlaying[i] = mNowPlaying[i + 1];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001176 i++;
1177 }
1178 onMove(-1, (int) mCurPos);
1179 } catch (RemoteException ex) {
1180 }
1181 return true;
1182 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001183
The Android Open Source Project792a2202009-03-03 19:32:30 -08001184 public void moveItem(int from, int to) {
1185 try {
1186 mService.moveQueueItem(from, to);
1187 mNowPlaying = mService.getQueue();
1188 onMove(-1, mCurPos); // update the underlying cursor
1189 } catch (RemoteException ex) {
1190 }
1191 }
1192
1193 private void dump() {
1194 String where = "(";
1195 for (int i = 0; i < mSize; i++) {
1196 where += mNowPlaying[i];
1197 if (i < mSize - 1) {
1198 where += ",";
1199 }
1200 }
1201 where += ")";
1202 Log.i("NowPlayingCursor: ", where);
1203 }
1204
1205 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001206 public String getString(int column) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001207 try {
1208 return mCurrentPlaylistCursor.getString(column);
1209 } catch (Exception ex) {
1210 onChange(true);
1211 return "";
1212 }
1213 }
1214
1215 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001216 public short getShort(int column) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001217 return mCurrentPlaylistCursor.getShort(column);
1218 }
1219
1220 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001221 public int getInt(int column) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001222 try {
1223 return mCurrentPlaylistCursor.getInt(column);
1224 } catch (Exception ex) {
1225 onChange(true);
1226 return 0;
1227 }
1228 }
1229
1230 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001231 public long getLong(int column) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001232 try {
1233 return mCurrentPlaylistCursor.getLong(column);
1234 } catch (Exception ex) {
1235 onChange(true);
1236 return 0;
1237 }
1238 }
1239
1240 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001241 public float getFloat(int column) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001242 return mCurrentPlaylistCursor.getFloat(column);
1243 }
1244
1245 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001246 public double getDouble(int column) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001247 return mCurrentPlaylistCursor.getDouble(column);
1248 }
1249
1250 @Override
Vasu Norif591aa92010-05-18 11:03:01 -07001251 public int getType(int column) {
1252 return mCurrentPlaylistCursor.getType(column);
1253 }
1254
1255 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001256 public boolean isNull(int column) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001257 return mCurrentPlaylistCursor.isNull(column);
1258 }
1259
1260 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001261 public String[] getColumnNames() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001262 return mCols;
1263 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001264
1265 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001266 public void deactivate() {
1267 if (mCurrentPlaylistCursor != null) mCurrentPlaylistCursor.deactivate();
1268 }
1269
1270 @Override
1271 public boolean requery() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001272 makeNowPlayingCursor();
1273 return true;
1274 }
1275
Jack Heb0fba8b2017-01-26 15:54:38 -08001276 private String[] mCols;
1277 private Cursor mCurrentPlaylistCursor; // updated in onMove
1278 private int mSize; // size of the queue
Marco Nelissenbd447b62009-06-29 14:52:05 -07001279 private long[] mNowPlaying;
1280 private long[] mCursorIdxs;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001281 private int mCurPos;
1282 private IMediaPlaybackService mService;
1283 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001284
The Android Open Source Project792a2202009-03-03 19:32:30 -08001285 static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer {
1286 boolean mIsNowPlaying;
1287 boolean mDisableNowPlayingIndicator;
1288
1289 int mTitleIdx;
1290 int mArtistIdx;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001291 int mDurationIdx;
1292 int mAudioIdIdx;
1293
1294 private final StringBuilder mBuilder = new StringBuilder();
1295 private final String mUnknownArtist;
1296 private final String mUnknownAlbum;
Jack Heb0fba8b2017-01-26 15:54:38 -08001297
The Android Open Source Project792a2202009-03-03 19:32:30 -08001298 private AlphabetIndexer mIndexer;
Jack Heb0fba8b2017-01-26 15:54:38 -08001299
The Android Open Source Project792a2202009-03-03 19:32:30 -08001300 private TrackBrowserActivity mActivity = null;
Marco Nelissen4248ed22009-07-30 08:28:49 -07001301 private TrackQueryHandler mQueryHandler;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001302 private String mConstraint = null;
1303 private boolean mConstraintIsValid = false;
Jack Heb0fba8b2017-01-26 15:54:38 -08001304
Marco Nelissen756c3f52009-05-14 10:07:23 -07001305 static class ViewHolder {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001306 TextView line1;
1307 TextView line2;
1308 TextView duration;
1309 ImageView play_indicator;
1310 CharArrayBuffer buffer1;
Jack Heb0fba8b2017-01-26 15:54:38 -08001311 char[] buffer2;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001312 }
1313
Marco Nelissen4248ed22009-07-30 08:28:49 -07001314 class TrackQueryHandler extends AsyncQueryHandler {
Marco Nelissene7887042009-07-30 10:40:49 -07001315 class QueryArgs {
1316 public Uri uri;
Jack Heb0fba8b2017-01-26 15:54:38 -08001317 public String[] projection;
Marco Nelissene7887042009-07-30 10:40:49 -07001318 public String selection;
Jack Heb0fba8b2017-01-26 15:54:38 -08001319 public String[] selectionArgs;
Marco Nelissene7887042009-07-30 10:40:49 -07001320 public String orderBy;
1321 }
1322
Marco Nelissen4248ed22009-07-30 08:28:49 -07001323 TrackQueryHandler(ContentResolver res) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001324 super(res);
1325 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001326
1327 public Cursor doQuery(Uri uri, String[] projection, String selection,
1328 String[] selectionArgs, String orderBy, boolean async) {
Marco Nelissen4248ed22009-07-30 08:28:49 -07001329 if (async) {
Marco Nelissene7887042009-07-30 10:40:49 -07001330 // Get 100 results first, which is enough to allow the user to start scrolling,
1331 // while still being very fast.
1332 Uri limituri = uri.buildUpon().appendQueryParameter("limit", "100").build();
1333 QueryArgs args = new QueryArgs();
1334 args.uri = uri;
1335 args.projection = projection;
1336 args.selection = selection;
1337 args.selectionArgs = selectionArgs;
1338 args.orderBy = orderBy;
1339
1340 startQuery(0, args, limituri, projection, selection, selectionArgs, orderBy);
Marco Nelissen4248ed22009-07-30 08:28:49 -07001341 return null;
1342 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001343 return MusicUtils.query(
1344 mActivity, uri, projection, selection, selectionArgs, orderBy);
Marco Nelissen4248ed22009-07-30 08:28:49 -07001345 }
1346
The Android Open Source Project792a2202009-03-03 19:32:30 -08001347 @Override
1348 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001349 // Log.i("@@@", "query complete: " + cursor.getCount() + " " + mActivity);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001350 mActivity.init(cursor, cookie != null);
Jack Heb0fba8b2017-01-26 15:54:38 -08001351 if (token == 0 && cookie != null && cursor != null && !cursor.isClosed()
1352 && cursor.getCount() >= 100) {
Marco Nelissene7887042009-07-30 10:40:49 -07001353 QueryArgs args = (QueryArgs) cookie;
1354 startQuery(1, null, args.uri, args.projection, args.selection,
1355 args.selectionArgs, args.orderBy);
1356 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001357 }
1358 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001359
1360 TrackListAdapter(Context context, TrackBrowserActivity currentactivity, int layout,
1361 Cursor cursor, String[] from, int[] to, boolean isnowplaying,
1362 boolean disablenowplayingindicator) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001363 super(context, layout, cursor, from, to);
1364 mActivity = currentactivity;
1365 getColumnIndices(cursor);
1366 mIsNowPlaying = isnowplaying;
1367 mDisableNowPlayingIndicator = disablenowplayingindicator;
1368 mUnknownArtist = context.getString(R.string.unknown_artist_name);
1369 mUnknownAlbum = context.getString(R.string.unknown_album_name);
Jack Heb0fba8b2017-01-26 15:54:38 -08001370
Marco Nelissen4248ed22009-07-30 08:28:49 -07001371 mQueryHandler = new TrackQueryHandler(context.getContentResolver());
The Android Open Source Project792a2202009-03-03 19:32:30 -08001372 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001373
The Android Open Source Project792a2202009-03-03 19:32:30 -08001374 public void setActivity(TrackBrowserActivity newactivity) {
1375 mActivity = newactivity;
1376 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001377
Marco Nelissen4248ed22009-07-30 08:28:49 -07001378 public TrackQueryHandler getQueryHandler() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001379 return mQueryHandler;
1380 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001381
The Android Open Source Project792a2202009-03-03 19:32:30 -08001382 private void getColumnIndices(Cursor cursor) {
1383 if (cursor != null) {
1384 mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
1385 mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001386 mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION);
1387 try {
1388 mAudioIdIdx = cursor.getColumnIndexOrThrow(
1389 MediaStore.Audio.Playlists.Members.AUDIO_ID);
1390 } catch (IllegalArgumentException ex) {
1391 mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
1392 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001393
The Android Open Source Project792a2202009-03-03 19:32:30 -08001394 if (mIndexer != null) {
1395 mIndexer.setCursor(cursor);
Marco Nelissen83a2e722010-09-17 09:39:54 -07001396 } else if (!mActivity.mEditMode && mActivity.mAlbumId == null) {
Marco Nelissen3d4b2622010-01-06 12:46:49 -08001397 String alpha = mActivity.getString(R.string.fast_scroll_alphabet);
Jack Heb0fba8b2017-01-26 15:54:38 -08001398
The Android Open Source Project792a2202009-03-03 19:32:30 -08001399 mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha);
1400 }
1401 }
1402 }
1403
1404 @Override
1405 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1406 View v = super.newView(context, cursor, parent);
1407 ImageView iv = (ImageView) v.findViewById(R.id.icon);
Marco Nelissen355e1342010-07-30 17:34:03 -07001408 iv.setVisibility(View.GONE);
Jack Heb0fba8b2017-01-26 15:54:38 -08001409
The Android Open Source Project792a2202009-03-03 19:32:30 -08001410 ViewHolder vh = new ViewHolder();
1411 vh.line1 = (TextView) v.findViewById(R.id.line1);
1412 vh.line2 = (TextView) v.findViewById(R.id.line2);
1413 vh.duration = (TextView) v.findViewById(R.id.duration);
1414 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
1415 vh.buffer1 = new CharArrayBuffer(100);
1416 vh.buffer2 = new char[200];
1417 v.setTag(vh);
1418 return v;
1419 }
1420
1421 @Override
1422 public void bindView(View view, Context context, Cursor cursor) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001423 ViewHolder vh = (ViewHolder) view.getTag();
Jack Heb0fba8b2017-01-26 15:54:38 -08001424
The Android Open Source Project792a2202009-03-03 19:32:30 -08001425 cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
1426 vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
Jack Heb0fba8b2017-01-26 15:54:38 -08001427
The Android Open Source Project792a2202009-03-03 19:32:30 -08001428 int secs = cursor.getInt(mDurationIdx) / 1000;
1429 if (secs == 0) {
1430 vh.duration.setText("");
1431 } else {
1432 vh.duration.setText(MusicUtils.makeTimeString(context, secs));
1433 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001434
The Android Open Source Project792a2202009-03-03 19:32:30 -08001435 final StringBuilder builder = mBuilder;
1436 builder.delete(0, builder.length());
1437
1438 String name = cursor.getString(mArtistIdx);
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001439 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001440 builder.append(mUnknownArtist);
1441 } else {
1442 builder.append(name);
1443 }
1444 int len = builder.length();
1445 if (vh.buffer2.length < len) {
1446 vh.buffer2 = new char[len];
1447 }
1448 builder.getChars(0, len, vh.buffer2, 0);
1449 vh.line2.setText(vh.buffer2, 0, len);
1450
1451 ImageView iv = vh.play_indicator;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001452 long id = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001453 if (MusicUtils.sService != null) {
1454 // TODO: IPC call on each bind??
1455 try {
1456 if (mIsNowPlaying) {
1457 id = MusicUtils.sService.getQueuePosition();
1458 } else {
1459 id = MusicUtils.sService.getAudioId();
1460 }
1461 } catch (RemoteException ex) {
1462 }
1463 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001464
The Android Open Source Project792a2202009-03-03 19:32:30 -08001465 // Determining whether and where to show the "now playing indicator
1466 // is tricky, because we don't actually keep track of where the songs
1467 // in the current playlist came from after they've started playing.
1468 //
1469 // If the "current playlists" is shown, then we can simply match by position,
1470 // otherwise, we need to match by id. Match-by-id gets a little weird if
1471 // a song appears in a playlist more than once, and you're in edit-playlist
1472 // mode. In that case, both items will have the "now playing" indicator.
1473 // For this reason, we don't show the play indicator at all when in edit
1474 // playlist mode (except when you're viewing the "current playlist",
1475 // which is not really a playlist)
Jack Heb0fba8b2017-01-26 15:54:38 -08001476 if ((mIsNowPlaying && cursor.getPosition() == id)
1477 || (!mIsNowPlaying && !mDisableNowPlayingIndicator
1478 && cursor.getLong(mAudioIdIdx) == id)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001479 iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
1480 iv.setVisibility(View.VISIBLE);
1481 } else {
1482 iv.setVisibility(View.GONE);
1483 }
1484 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001485
The Android Open Source Project792a2202009-03-03 19:32:30 -08001486 @Override
1487 public void changeCursor(Cursor cursor) {
Marco Nelissend99bc1e2009-12-04 08:48:06 -08001488 if (mActivity.isFinishing() && cursor != null) {
1489 cursor.close();
1490 cursor = null;
Marco Nelissen0e175782009-12-03 14:24:53 -08001491 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001492 if (cursor != mActivity.mTrackCursor) {
1493 mActivity.mTrackCursor = cursor;
1494 super.changeCursor(cursor);
1495 getColumnIndices(cursor);
1496 }
1497 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001498
The Android Open Source Project792a2202009-03-03 19:32:30 -08001499 @Override
1500 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1501 String s = constraint.toString();
Jack Heb0fba8b2017-01-26 15:54:38 -08001502 if (mConstraintIsValid && ((s == null && mConstraint == null)
1503 || (s != null && s.equals(mConstraint)))) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001504 return getCursor();
1505 }
Marco Nelissen4248ed22009-07-30 08:28:49 -07001506 Cursor c = mActivity.getTrackCursor(mQueryHandler, s, false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001507 mConstraint = s;
1508 mConstraintIsValid = true;
1509 return c;
1510 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001511
The Android Open Source Project792a2202009-03-03 19:32:30 -08001512 // SectionIndexer methods
Jack Heb0fba8b2017-01-26 15:54:38 -08001513
The Android Open Source Project792a2202009-03-03 19:32:30 -08001514 public Object[] getSections() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001515 if (mIndexer != null) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001516 return mIndexer.getSections();
1517 } else {
Jack Heb0fba8b2017-01-26 15:54:38 -08001518 return new String[] {" "};
The Android Open Source Project792a2202009-03-03 19:32:30 -08001519 }
1520 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001521
The Android Open Source Project792a2202009-03-03 19:32:30 -08001522 public int getPositionForSection(int section) {
Marco Nelissen6b9d4582011-06-07 10:57:58 -07001523 if (mIndexer != null) {
1524 return mIndexer.getPositionForSection(section);
1525 }
1526 return 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001527 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001528
The Android Open Source Project792a2202009-03-03 19:32:30 -08001529 public int getSectionForPosition(int position) {
1530 return 0;
Jack Heb0fba8b2017-01-26 15:54:38 -08001531 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001532 }
1533}