blob: 76a02de142d9f72e590570c276bf0652fad4b025 [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
69 implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection
70{
Marco Nelissen756c3f52009-05-14 10:07:23 -070071 private static final int Q_SELECTED = CHILD_MENU_BASE;
72 private static final int Q_ALL = CHILD_MENU_BASE + 1;
73 private static final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
74 private static final int PLAY_ALL = CHILD_MENU_BASE + 3;
75 private static final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
76 private static final int REMOVE = CHILD_MENU_BASE + 5;
77 private static final int SEARCH = CHILD_MENU_BASE + 6;
The Android Open Source Project792a2202009-03-03 19:32:30 -080078
79
80 private static final String LOGTAG = "TrackBrowser";
81
82 private String[] mCursorCols;
83 private String[] mPlaylistMemberCols;
84 private boolean mDeletedOneRow = false;
85 private boolean mEditMode = false;
86 private String mCurrentTrackName;
87 private String mCurrentAlbumName;
88 private String mCurrentArtistNameForAlbum;
89 private ListView mTrackList;
90 private Cursor mTrackCursor;
91 private TrackListAdapter mAdapter;
92 private boolean mAdapterSent = false;
93 private String mAlbumId;
94 private String mArtistId;
95 private String mPlaylist;
96 private String mGenre;
97 private String mSortOrder;
98 private int mSelectedPosition;
99 private long mSelectedId;
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800100 private static int mLastListPosCourse = -1;
101 private static int mLastListPosFine = -1;
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800102 private boolean mUseLastListPos = false;
Marco Nelissenf33a5752010-01-27 15:35:43 -0800103 private ServiceToken mToken;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800104
105 public TrackBrowserActivity()
106 {
107 }
108
109 /** Called when the activity is first created. */
110 @Override
111 public void onCreate(Bundle icicle)
112 {
113 super.onCreate(icicle);
114 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800115 Intent intent = getIntent();
116 if (intent != null) {
117 if (intent.getBooleanExtra("withtabs", false)) {
118 requestWindowFeature(Window.FEATURE_NO_TITLE);
119 }
120 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800121 setVolumeControlStream(AudioManager.STREAM_MUSIC);
122 if (icicle != null) {
123 mSelectedId = icicle.getLong("selectedtrack");
124 mAlbumId = icicle.getString("album");
125 mArtistId = icicle.getString("artist");
126 mPlaylist = icicle.getString("playlist");
127 mGenre = icicle.getString("genre");
128 mEditMode = icicle.getBoolean("editmode", false);
129 } else {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800130 mAlbumId = intent.getStringExtra("album");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800131 // If we have an album, show everything on the album, not just stuff
132 // by a particular artist.
The Android Open Source Project792a2202009-03-03 19:32:30 -0800133 mArtistId = intent.getStringExtra("artist");
134 mPlaylist = intent.getStringExtra("playlist");
135 mGenre = intent.getStringExtra("genre");
136 mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
137 }
138
139 mCursorCols = new String[] {
140 MediaStore.Audio.Media._ID,
141 MediaStore.Audio.Media.TITLE,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800142 MediaStore.Audio.Media.DATA,
143 MediaStore.Audio.Media.ALBUM,
144 MediaStore.Audio.Media.ARTIST,
145 MediaStore.Audio.Media.ARTIST_ID,
146 MediaStore.Audio.Media.DURATION
147 };
148 mPlaylistMemberCols = new String[] {
149 MediaStore.Audio.Playlists.Members._ID,
150 MediaStore.Audio.Media.TITLE,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800151 MediaStore.Audio.Media.DATA,
152 MediaStore.Audio.Media.ALBUM,
153 MediaStore.Audio.Media.ARTIST,
154 MediaStore.Audio.Media.ARTIST_ID,
155 MediaStore.Audio.Media.DURATION,
156 MediaStore.Audio.Playlists.Members.PLAY_ORDER,
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700157 MediaStore.Audio.Playlists.Members.AUDIO_ID,
158 MediaStore.Audio.Media.IS_MUSIC
The Android Open Source Project792a2202009-03-03 19:32:30 -0800159 };
160
161 setContentView(R.layout.media_picker_activity);
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800162 mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800163 mTrackList = getListView();
164 mTrackList.setOnCreateContextMenuListener(this);
Marco Nelissen355e1342010-07-30 17:34:03 -0700165 mTrackList.setCacheColorHint(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800166 if (mEditMode) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800167 ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
168 ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
Marco Nelissen355e1342010-07-30 17:34:03 -0700169 mTrackList.setDivider(null);
170 mTrackList.setSelector(R.drawable.list_selector_background);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800171 } else {
172 mTrackList.setTextFilterEnabled(true);
173 }
174 mAdapter = (TrackListAdapter) getLastNonConfigurationInstance();
175
176 if (mAdapter != null) {
177 mAdapter.setActivity(this);
178 setListAdapter(mAdapter);
179 }
Marco Nelissenf33a5752010-01-27 15:35:43 -0800180 mToken = MusicUtils.bindToService(this, this);
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800181
182 // don't set the album art until after the view has been layed out
183 mTrackList.post(new Runnable() {
184
185 public void run() {
186 setAlbumArtBackground();
187 }
188 });
The Android Open Source Project792a2202009-03-03 19:32:30 -0800189 }
190
191 public void onServiceConnected(ComponentName name, IBinder service)
192 {
193 IntentFilter f = new IntentFilter();
194 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
195 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
196 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
197 f.addDataScheme("file");
198 registerReceiver(mScanListener, f);
199
200 if (mAdapter == null) {
201 //Log.i("@@@", "starting query");
202 mAdapter = new TrackListAdapter(
203 getApplication(), // need to use application context to avoid leaks
204 this,
205 mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item,
206 null, // cursor
207 new String[] {},
208 new int[] {},
209 "nowplaying".equals(mPlaylist),
210 mPlaylist != null &&
211 !(mPlaylist.equals("podcasts") || mPlaylist.equals("recentlyadded")));
212 setListAdapter(mAdapter);
213 setTitle(R.string.working_songs);
Marco Nelissen4248ed22009-07-30 08:28:49 -0700214 getTrackCursor(mAdapter.getQueryHandler(), null, true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800215 } else {
216 mTrackCursor = mAdapter.getCursor();
217 // If mTrackCursor is null, this can be because it doesn't have
218 // a cursor yet (because the initial query that sets its cursor
219 // is still in progress), or because the query failed.
220 // In order to not flash the error dialog at the user for the
221 // first case, simply retry the query when the cursor is null.
222 // Worst case, we end up doing the same query twice.
223 if (mTrackCursor != null) {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800224 init(mTrackCursor, false);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800225 } else {
226 setTitle(R.string.working_songs);
Marco Nelissen4248ed22009-07-30 08:28:49 -0700227 getTrackCursor(mAdapter.getQueryHandler(), null, true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800228 }
229 }
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800230 if (!mEditMode) {
231 MusicUtils.updateNowPlaying(this);
232 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800233 }
234
235 public void onServiceDisconnected(ComponentName name) {
236 // we can't really function without the service, so don't
237 finish();
238 }
239
240 @Override
241 public Object onRetainNonConfigurationInstance() {
242 TrackListAdapter a = mAdapter;
243 mAdapterSent = true;
244 return a;
245 }
246
247 @Override
248 public void onDestroy() {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800249 ListView lv = getListView();
Marco Nelissend7d9ba52010-02-12 14:43:25 -0800250 if (lv != null) {
251 if (mUseLastListPos) {
252 mLastListPosCourse = lv.getFirstVisiblePosition();
253 View cv = lv.getChildAt(0);
254 if (cv != null) {
255 mLastListPosFine = cv.getTop();
256 }
257 }
258 if (mEditMode) {
259 // clear the listeners so we won't get any more callbacks
260 ((TouchInterceptor) lv).setDropListener(null);
261 ((TouchInterceptor) lv).setRemoveListener(null);
Marco Nelissen23b531e2009-12-12 14:07:59 -0800262 }
Marco Nelissen9e0f1fd2009-12-12 13:41:34 -0800263 }
Marco Nelissend7d9ba52010-02-12 14:43:25 -0800264
Marco Nelissenf33a5752010-01-27 15:35:43 -0800265 MusicUtils.unbindFromService(mToken);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800266 try {
267 if ("nowplaying".equals(mPlaylist)) {
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700268 unregisterReceiverSafe(mNowPlayingListener);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800269 } else {
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700270 unregisterReceiverSafe(mTrackListListener);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800271 }
272 } catch (IllegalArgumentException ex) {
273 // we end up here in case we never registered the listeners
274 }
275
Marco Nelissen0e175782009-12-03 14:24:53 -0800276 // If we have an adapter and didn't send it off to another activity yet, we should
277 // close its cursor, which we do by assigning a null cursor to it. Doing this
278 // instead of closing the cursor directly keeps the framework from accessing
279 // the closed cursor later.
Marco Nelissen8e732ff2009-10-12 12:29:09 -0700280 if (!mAdapterSent && mAdapter != null) {
Marco Nelissen0e175782009-12-03 14:24:53 -0800281 mAdapter.changeCursor(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800282 }
Marco Nelissen0164ebf2009-08-13 09:58:43 -0700283 // Because we pass the adapter to the next activity, we need to make
284 // sure it doesn't keep a reference to this activity. We can do this
285 // by clearing its DatasetObservers, which setListAdapter(null) does.
286 setListAdapter(null);
287 mAdapter = null;
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700288 unregisterReceiverSafe(mScanListener);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800289 super.onDestroy();
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700290 }
291
292 /**
293 * Unregister a receiver, but eat the exception that is thrown if the
294 * receiver was never registered to begin with. This is a little easier
295 * than keeping track of whether the receivers have actually been
296 * registered by the time onDestroy() is called.
297 */
298 private void unregisterReceiverSafe(BroadcastReceiver receiver) {
299 try {
300 unregisterReceiver(receiver);
301 } catch (IllegalArgumentException e) {
302 // ignore
303 }
304 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800305
306 @Override
307 public void onResume() {
308 super.onResume();
309 if (mTrackCursor != null) {
310 getListView().invalidateViews();
311 }
312 MusicUtils.setSpinnerState(this);
313 }
314 @Override
315 public void onPause() {
316 mReScanHandler.removeCallbacksAndMessages(null);
317 super.onPause();
318 }
319
320 /*
321 * This listener gets called when the media scanner starts up or finishes, and
322 * when the sd card is unmounted.
323 */
324 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
325 @Override
326 public void onReceive(Context context, Intent intent) {
327 String action = intent.getAction();
328 if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action) ||
329 Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
330 MusicUtils.setSpinnerState(TrackBrowserActivity.this);
331 }
332 mReScanHandler.sendEmptyMessage(0);
333 }
334 };
335
336 private Handler mReScanHandler = new Handler() {
337 @Override
338 public void handleMessage(Message msg) {
Marco Nelissen42bcc212009-09-01 13:22:19 -0700339 if (mAdapter != null) {
340 getTrackCursor(mAdapter.getQueryHandler(), null, true);
341 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800342 // if the query results in a null cursor, onQueryComplete() will
343 // call init(), which will post a delayed message to this handler
344 // in order to try again.
345 }
346 };
347
348 public void onSaveInstanceState(Bundle outcicle) {
349 // need to store the selected item so we don't lose it in case
350 // of an orientation switch. Otherwise we could lose it while
351 // in the middle of specifying a playlist to add the item to.
352 outcicle.putLong("selectedtrack", mSelectedId);
353 outcicle.putString("artist", mArtistId);
354 outcicle.putString("album", mAlbumId);
355 outcicle.putString("playlist", mPlaylist);
356 outcicle.putString("genre", mGenre);
357 outcicle.putBoolean("editmode", mEditMode);
358 super.onSaveInstanceState(outcicle);
359 }
360
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800361 public void init(Cursor newCursor, boolean isLimited) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800362
Marco Nelissen42bcc212009-09-01 13:22:19 -0700363 if (mAdapter == null) {
364 return;
365 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800366 mAdapter.changeCursor(newCursor); // also sets mTrackCursor
367
368 if (mTrackCursor == null) {
369 MusicUtils.displayDatabaseError(this);
370 closeContextMenu();
371 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
372 return;
373 }
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800374
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800375 MusicUtils.hideDatabaseError(this);
376 mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
377 setTitle();
378
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800379 // Restore previous position
Marco Nelissen19cea9e2009-12-14 15:59:30 -0800380 if (mLastListPosCourse >= 0 && mUseLastListPos) {
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800381 ListView lv = getListView();
382 // this hack is needed because otherwise the position doesn't change
383 // for the 2nd (non-limited) cursor
384 lv.setAdapter(lv.getAdapter());
385 lv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
386 if (!isLimited) {
387 mLastListPosCourse = -1;
388 }
389 }
390
The Android Open Source Project792a2202009-03-03 19:32:30 -0800391 // When showing the queue, position the selection on the currently playing track
392 // Otherwise, position the selection on the first matching artist, if any
393 IntentFilter f = new IntentFilter();
394 f.addAction(MediaPlaybackService.META_CHANGED);
395 f.addAction(MediaPlaybackService.QUEUE_CHANGED);
396 if ("nowplaying".equals(mPlaylist)) {
397 try {
398 int cur = MusicUtils.sService.getQueuePosition();
399 setSelection(cur);
400 registerReceiver(mNowPlayingListener, new IntentFilter(f));
401 mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
402 } catch (RemoteException ex) {
403 }
404 } else {
405 String key = getIntent().getStringExtra("artist");
406 if (key != null) {
407 int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID);
408 mTrackCursor.moveToFirst();
409 while (! mTrackCursor.isAfterLast()) {
410 String artist = mTrackCursor.getString(keyidx);
411 if (artist.equals(key)) {
412 setSelection(mTrackCursor.getPosition());
413 break;
414 }
415 mTrackCursor.moveToNext();
416 }
417 }
418 registerReceiver(mTrackListListener, new IntentFilter(f));
419 mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
420 }
421 }
422
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800423 private void setAlbumArtBackground() {
Marco Nelissen355e1342010-07-30 17:34:03 -0700424 if (!mEditMode) {
425 try {
426 long albumid = Long.valueOf(mAlbumId);
427 Bitmap bm = MusicUtils.getArtwork(TrackBrowserActivity.this, -1, albumid, false);
428 if (bm != null) {
429 MusicUtils.setBackground(mTrackList, bm);
430 mTrackList.setCacheColorHint(0);
431 return;
432 }
433 } catch (Exception ex) {
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800434 }
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800435 }
Marco Nelissen355e1342010-07-30 17:34:03 -0700436 mTrackList.setBackgroundColor(0xff000000);
437 mTrackList.setCacheColorHint(0);
Marco Nelissen3193d7d2009-12-14 15:31:28 -0800438 }
439
The Android Open Source Project792a2202009-03-03 19:32:30 -0800440 private void setTitle() {
441
442 CharSequence fancyName = null;
443 if (mAlbumId != null) {
444 int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0;
445 if (numresults > 0) {
446 mTrackCursor.moveToFirst();
447 int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
448 fancyName = mTrackCursor.getString(idx);
449 // For compilation albums show only the album title,
450 // but for regular albums show "artist - album".
451 // To determine whether something is a compilation
452 // album, do a query for the artist + album of the
453 // first item, and see if it returns the same number
454 // of results as the album query.
455 String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId +
456 "' AND " + MediaStore.Audio.Media.ARTIST_ID + "=" +
457 mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow(
458 MediaStore.Audio.Media.ARTIST_ID));
459 Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
460 new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null);
461 if (cursor != null) {
462 if (cursor.getCount() != numresults) {
463 // compilation album
464 fancyName = mTrackCursor.getString(idx);
465 }
466 cursor.deactivate();
467 }
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800468 if (fancyName == null || fancyName.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800469 fancyName = getString(R.string.unknown_album_name);
470 }
471 }
472 } else if (mPlaylist != null) {
473 if (mPlaylist.equals("nowplaying")) {
474 if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) {
475 fancyName = getText(R.string.partyshuffle_title);
476 } else {
477 fancyName = getText(R.string.nowplaying_title);
478 }
479 } else if (mPlaylist.equals("podcasts")){
480 fancyName = getText(R.string.podcasts_title);
481 } else if (mPlaylist.equals("recentlyadded")){
482 fancyName = getText(R.string.recentlyadded_title);
483 } else {
484 String [] cols = new String [] {
485 MediaStore.Audio.Playlists.NAME
486 };
487 Cursor cursor = MusicUtils.query(this,
488 ContentUris.withAppendedId(Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)),
489 cols, null, null, null);
490 if (cursor != null) {
491 if (cursor.getCount() != 0) {
492 cursor.moveToFirst();
493 fancyName = cursor.getString(0);
494 }
495 cursor.deactivate();
496 }
497 }
498 } else if (mGenre != null) {
499 String [] cols = new String [] {
500 MediaStore.Audio.Genres.NAME
501 };
502 Cursor cursor = MusicUtils.query(this,
503 ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)),
504 cols, null, null, null);
505 if (cursor != null) {
506 if (cursor.getCount() != 0) {
507 cursor.moveToFirst();
508 fancyName = cursor.getString(0);
509 }
510 cursor.deactivate();
511 }
512 }
513
514 if (fancyName != null) {
515 setTitle(fancyName);
516 } else {
517 setTitle(R.string.tracks_title);
518 }
519 }
520
The Android Open Source Project792a2202009-03-03 19:32:30 -0800521 private TouchInterceptor.DropListener mDropListener =
522 new TouchInterceptor.DropListener() {
523 public void drop(int from, int to) {
524 if (mTrackCursor instanceof NowPlayingCursor) {
525 // update the currently playing list
526 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
527 c.moveItem(from, to);
528 ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
529 getListView().invalidateViews();
530 mDeletedOneRow = true;
531 } else {
532 // update a saved playlist
Marco Nelissen57934552009-12-09 09:32:42 -0800533 MediaStore.Audio.Playlists.Members.moveItem(getContentResolver(),
534 Long.valueOf(mPlaylist), from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800535 }
536 }
537 };
538
539 private TouchInterceptor.RemoveListener mRemoveListener =
540 new TouchInterceptor.RemoveListener() {
541 public void remove(int which) {
542 removePlaylistItem(which);
543 }
544 };
545
546 private void removePlaylistItem(int which) {
547 View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
Marco Nelissen42131382009-12-04 09:40:22 -0800548 if (v == null) {
549 Log.d(LOGTAG, "No view when removing playlist item " + which);
550 return;
551 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800552 try {
553 if (MusicUtils.sService != null
554 && which != MusicUtils.sService.getQueuePosition()) {
555 mDeletedOneRow = true;
556 }
557 } catch (RemoteException e) {
558 // Service died, so nothing playing.
559 mDeletedOneRow = true;
560 }
561 v.setVisibility(View.GONE);
562 mTrackList.invalidateViews();
563 if (mTrackCursor instanceof NowPlayingCursor) {
564 ((NowPlayingCursor)mTrackCursor).removeItem(which);
565 } else {
566 int colidx = mTrackCursor.getColumnIndexOrThrow(
567 MediaStore.Audio.Playlists.Members._ID);
568 mTrackCursor.moveToPosition(which);
569 long id = mTrackCursor.getLong(colidx);
570 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
571 Long.valueOf(mPlaylist));
572 getContentResolver().delete(
573 ContentUris.withAppendedId(uri, id), null, null);
574 }
575 v.setVisibility(View.VISIBLE);
576 mTrackList.invalidateViews();
577 }
578
579 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
580 @Override
581 public void onReceive(Context context, Intent intent) {
582 getListView().invalidateViews();
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800583 if (!mEditMode) {
584 MusicUtils.updateNowPlaying(TrackBrowserActivity.this);
585 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800586 }
587 };
588
589 private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() {
590 @Override
591 public void onReceive(Context context, Intent intent) {
592 if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) {
593 getListView().invalidateViews();
594 } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) {
595 if (mDeletedOneRow) {
596 // This is the notification for a single row that was
597 // deleted previously, which is already reflected in
598 // the UI.
599 mDeletedOneRow = false;
600 return;
601 }
Marco Nelissen04b29c92010-02-11 17:49:53 -0800602 // The service could disappear while the broadcast was in flight,
603 // so check to see if it's still valid
604 if (MusicUtils.sService == null) {
605 finish();
606 return;
607 }
Marco Nelissena51d6fe2010-08-31 15:24:18 -0700608 if (mAdapter != null) {
609 Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
610 if (c.getCount() == 0) {
611 finish();
612 return;
613 }
614 mAdapter.changeCursor(c);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800615 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800616 }
617 }
618 };
619
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700620 // Cursor should be positioned on the entry to be checked
621 // Returns false if the entry matches the naming pattern used for recordings,
622 // or if it is marked as not music in the database.
623 private boolean isMusic(Cursor c) {
624 int titleidx = c.getColumnIndex(MediaStore.Audio.Media.TITLE);
625 int albumidx = c.getColumnIndex(MediaStore.Audio.Media.ALBUM);
626 int artistidx = c.getColumnIndex(MediaStore.Audio.Media.ARTIST);
627
628 String title = c.getString(titleidx);
629 String album = c.getString(albumidx);
630 String artist = c.getString(artistidx);
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800631 if (MediaStore.UNKNOWN_STRING.equals(album) &&
632 MediaStore.UNKNOWN_STRING.equals(artist) &&
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700633 title != null &&
634 title.startsWith("recording")) {
635 // not music
636 return false;
637 }
638
639 int ismusic_idx = c.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC);
640 boolean ismusic = true;
641 if (ismusic_idx >= 0) {
642 ismusic = mTrackCursor.getInt(ismusic_idx) != 0;
643 }
644 return ismusic;
645 }
646
The Android Open Source Project792a2202009-03-03 19:32:30 -0800647 @Override
648 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
649 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
650 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
651 MusicUtils.makePlaylistMenu(this, sub);
652 if (mEditMode) {
653 menu.add(0, REMOVE, 0, R.string.remove_from_playlist);
654 }
655 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu);
656 menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800657 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
658 mSelectedPosition = mi.position;
659 mTrackCursor.moveToPosition(mSelectedPosition);
660 try {
661 int id_idx = mTrackCursor.getColumnIndexOrThrow(
662 MediaStore.Audio.Playlists.Members.AUDIO_ID);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700663 mSelectedId = mTrackCursor.getLong(id_idx);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800664 } catch (IllegalArgumentException ex) {
665 mSelectedId = mi.id;
666 }
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700667 // only add the 'search' menu if the selected item is music
668 if (isMusic(mTrackCursor)) {
669 menu.add(0, SEARCH, 0, R.string.search_title);
670 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800671 mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
672 MediaStore.Audio.Media.ALBUM));
673 mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
674 MediaStore.Audio.Media.ARTIST));
675 mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
676 MediaStore.Audio.Media.TITLE));
677 menu.setHeaderTitle(mCurrentTrackName);
678 }
679
680 @Override
681 public boolean onContextItemSelected(MenuItem item) {
682 switch (item.getItemId()) {
683 case PLAY_SELECTION: {
684 // play the track
685 int position = mSelectedPosition;
686 MusicUtils.playAll(this, mTrackCursor, position);
687 return true;
688 }
689
690 case QUEUE: {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700691 long [] list = new long[] { mSelectedId };
The Android Open Source Project792a2202009-03-03 19:32:30 -0800692 MusicUtils.addToCurrentPlaylist(this, list);
693 return true;
694 }
695
696 case NEW_PLAYLIST: {
697 Intent intent = new Intent();
698 intent.setClass(this, CreatePlaylist.class);
699 startActivityForResult(intent, NEW_PLAYLIST);
700 return true;
701 }
702
703 case PLAYLIST_SELECTED: {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700704 long [] list = new long[] { mSelectedId };
705 long playlist = item.getIntent().getLongExtra("playlist", 0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800706 MusicUtils.addToPlaylist(this, list, playlist);
707 return true;
708 }
709
710 case USE_AS_RINGTONE:
711 // Set the system setting to make this the current ringtone
712 MusicUtils.setRingtone(this, mSelectedId);
713 return true;
714
715 case DELETE_ITEM: {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700716 long [] list = new long[1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800717 list[0] = (int) mSelectedId;
718 Bundle b = new Bundle();
Eric Fischerecf5b652010-10-18 14:20:30 -0700719 String f;
720 if (android.os.Environment.isExternalStorageRemovable()) {
721 f = getString(R.string.delete_song_desc);
722 } else {
723 f = getString(R.string.delete_song_desc_nosdcard);
724 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800725 String desc = String.format(f, mCurrentTrackName);
726 b.putString("description", desc);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700727 b.putLongArray("items", list);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800728 Intent intent = new Intent();
729 intent.setClass(this, DeleteItems.class);
730 intent.putExtras(b);
731 startActivityForResult(intent, -1);
732 return true;
733 }
734
735 case REMOVE:
736 removePlaylistItem(mSelectedPosition);
737 return true;
738
739 case SEARCH:
740 doSearch();
741 return true;
742 }
743 return super.onContextItemSelected(item);
744 }
745
746 void doSearch() {
747 CharSequence title = null;
748 String query = null;
749
750 Intent i = new Intent();
751 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
Marco Nelissen4341b502009-05-06 14:57:37 -0700752 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800753
Marco Nelissen9882f542009-08-14 10:06:15 -0700754 title = mCurrentTrackName;
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800755 if (MediaStore.UNKNOWN_STRING.equals(mCurrentArtistNameForAlbum)) {
Marco Nelissen9882f542009-08-14 10:06:15 -0700756 query = mCurrentTrackName;
757 } else {
758 query = mCurrentArtistNameForAlbum + " " + mCurrentTrackName;
759 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
760 }
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800761 if (MediaStore.UNKNOWN_STRING.equals(mCurrentAlbumName)) {
Marco Nelissen9882f542009-08-14 10:06:15 -0700762 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
763 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800764 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*");
765 title = getString(R.string.mediasearch, title);
766 i.putExtra(SearchManager.QUERY, query);
767
768 startActivity(Intent.createChooser(i, title));
769 }
770
771 // In order to use alt-up/down as a shortcut for moving the selected item
772 // in the list, we need to override dispatchKeyEvent, not onKeyDown.
773 // (onKeyDown never sees these events, since they are handled by the list)
774 @Override
775 public boolean dispatchKeyEvent(KeyEvent event) {
776 if (mPlaylist != null && event.getMetaState() != 0 &&
777 event.getAction() == KeyEvent.ACTION_DOWN) {
778 switch (event.getKeyCode()) {
779 case KeyEvent.KEYCODE_DPAD_UP:
780 moveItem(true);
781 return true;
782 case KeyEvent.KEYCODE_DPAD_DOWN:
783 moveItem(false);
784 return true;
785 case KeyEvent.KEYCODE_DEL:
786 removeItem();
787 return true;
788 }
789 }
790
791 return super.dispatchKeyEvent(event);
792 }
793
794 private void removeItem() {
795 int curcount = mTrackCursor.getCount();
796 int curpos = mTrackList.getSelectedItemPosition();
797 if (curcount == 0 || curpos < 0) {
798 return;
799 }
800
801 if ("nowplaying".equals(mPlaylist)) {
802 // remove track from queue
803
804 // Work around bug 902971. To get quick visual feedback
805 // of the deletion of the item, hide the selected view.
806 try {
807 if (curpos != MusicUtils.sService.getQueuePosition()) {
808 mDeletedOneRow = true;
809 }
810 } catch (RemoteException ex) {
811 }
812 View v = mTrackList.getSelectedView();
813 v.setVisibility(View.GONE);
814 mTrackList.invalidateViews();
815 ((NowPlayingCursor)mTrackCursor).removeItem(curpos);
816 v.setVisibility(View.VISIBLE);
817 mTrackList.invalidateViews();
818 } else {
819 // remove track from playlist
820 int colidx = mTrackCursor.getColumnIndexOrThrow(
821 MediaStore.Audio.Playlists.Members._ID);
822 mTrackCursor.moveToPosition(curpos);
823 long id = mTrackCursor.getLong(colidx);
824 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
825 Long.valueOf(mPlaylist));
826 getContentResolver().delete(
827 ContentUris.withAppendedId(uri, id), null, null);
828 curcount--;
829 if (curcount == 0) {
830 finish();
831 } else {
832 mTrackList.setSelection(curpos < curcount ? curpos : curcount);
833 }
834 }
835 }
836
837 private void moveItem(boolean up) {
838 int curcount = mTrackCursor.getCount();
839 int curpos = mTrackList.getSelectedItemPosition();
840 if ( (up && curpos < 1) || (!up && curpos >= curcount - 1)) {
841 return;
842 }
843
844 if (mTrackCursor instanceof NowPlayingCursor) {
845 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
846 c.moveItem(curpos, up ? curpos - 1 : curpos + 1);
847 ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
848 getListView().invalidateViews();
849 mDeletedOneRow = true;
850 if (up) {
851 mTrackList.setSelection(curpos - 1);
852 } else {
853 mTrackList.setSelection(curpos + 1);
854 }
855 } else {
856 int colidx = mTrackCursor.getColumnIndexOrThrow(
857 MediaStore.Audio.Playlists.Members.PLAY_ORDER);
858 mTrackCursor.moveToPosition(curpos);
859 int currentplayidx = mTrackCursor.getInt(colidx);
860 Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
861 Long.valueOf(mPlaylist));
862 ContentValues values = new ContentValues();
863 String where = MediaStore.Audio.Playlists.Members._ID + "=?";
864 String [] wherearg = new String[1];
865 ContentResolver res = getContentResolver();
866 if (up) {
867 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1);
868 wherearg[0] = mTrackCursor.getString(0);
869 res.update(baseUri, values, where, wherearg);
870 mTrackCursor.moveToPrevious();
871 } else {
872 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1);
873 wherearg[0] = mTrackCursor.getString(0);
874 res.update(baseUri, values, where, wherearg);
875 mTrackCursor.moveToNext();
876 }
877 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx);
878 wherearg[0] = mTrackCursor.getString(0);
879 res.update(baseUri, values, where, wherearg);
880 }
881 }
882
883 @Override
884 protected void onListItemClick(ListView l, View v, int position, long id)
885 {
886 if (mTrackCursor.getCount() == 0) {
887 return;
888 }
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800889 // When selecting a track from the queue, just jump there instead of
890 // reloading the queue. This is both faster, and prevents accidentally
891 // dropping out of party shuffle.
892 if (mTrackCursor instanceof NowPlayingCursor) {
893 if (MusicUtils.sService != null) {
894 try {
895 MusicUtils.sService.setQueuePosition(position);
896 return;
897 } catch (RemoteException ex) {
898 }
899 }
900 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800901 MusicUtils.playAll(this, mTrackCursor, position);
902 }
903
904 @Override
905 public boolean onCreateOptionsMenu(Menu menu) {
906 /* This activity is used for a number of different browsing modes, and the menu can
907 * be different for each of them:
908 * - all tracks, optionally restricted to an album, artist or playlist
909 * - the list of currently playing songs
910 */
911 super.onCreateOptionsMenu(menu);
912 if (mPlaylist == null) {
Marco Nelissen3d4b2622010-01-06 12:46:49 -0800913 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 -0800914 }
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800915 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
The Android Open Source Project792a2202009-03-03 19:32:30 -0800916 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
917 if (mPlaylist != null) {
918 menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(android.R.drawable.ic_menu_save);
919 if (mPlaylist.equals("nowplaying")) {
Marco Nelissen3d4b2622010-01-06 12:46:49 -0800920 menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist).setIcon(R.drawable.ic_menu_clear_playlist);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800921 }
922 }
923 return true;
924 }
925
926 @Override
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800927 public boolean onPrepareOptionsMenu(Menu menu) {
928 MusicUtils.setPartyShuffleMenuIcon(menu);
929 return super.onPrepareOptionsMenu(menu);
930 }
931
932 @Override
The Android Open Source Project792a2202009-03-03 19:32:30 -0800933 public boolean onOptionsItemSelected(MenuItem item) {
934 Intent intent;
935 Cursor cursor;
936 switch (item.getItemId()) {
937 case PLAY_ALL: {
938 MusicUtils.playAll(this, mTrackCursor);
939 return true;
940 }
941
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800942 case PARTY_SHUFFLE:
943 MusicUtils.togglePartyShuffle();
944 break;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800945
946 case SHUFFLE_ALL:
947 // Should 'shuffle all' shuffle ALL, or only the tracks shown?
948 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
949 new String [] { MediaStore.Audio.Media._ID},
950 MediaStore.Audio.Media.IS_MUSIC + "=1", null,
951 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
952 if (cursor != null) {
953 MusicUtils.shuffleAll(this, cursor);
954 cursor.close();
955 }
956 return true;
957
958 case SAVE_AS_PLAYLIST:
959 intent = new Intent();
960 intent.setClass(this, CreatePlaylist.class);
961 startActivityForResult(intent, SAVE_AS_PLAYLIST);
962 return true;
963
964 case CLEAR_PLAYLIST:
965 // We only clear the current playlist
966 MusicUtils.clearQueue();
967 return true;
968 }
969 return super.onOptionsItemSelected(item);
970 }
971
972 @Override
973 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
974 switch (requestCode) {
975 case SCAN_DONE:
976 if (resultCode == RESULT_CANCELED) {
977 finish();
978 } else {
Marco Nelissen4248ed22009-07-30 08:28:49 -0700979 getTrackCursor(mAdapter.getQueryHandler(), null, true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800980 }
981 break;
982
983 case NEW_PLAYLIST:
984 if (resultCode == RESULT_OK) {
985 Uri uri = intent.getData();
986 if (uri != null) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700987 long [] list = new long[] { mSelectedId };
The Android Open Source Project792a2202009-03-03 19:32:30 -0800988 MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment()));
989 }
990 }
991 break;
992
993 case SAVE_AS_PLAYLIST:
994 if (resultCode == RESULT_OK) {
995 Uri uri = intent.getData();
996 if (uri != null) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700997 long [] list = MusicUtils.getSongListForCursor(mTrackCursor);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800998 int plid = Integer.parseInt(uri.getLastPathSegment());
999 MusicUtils.addToPlaylist(this, list, plid);
1000 }
1001 }
1002 break;
1003 }
1004 }
1005
Marco Nelissen4248ed22009-07-30 08:28:49 -07001006 private Cursor getTrackCursor(TrackListAdapter.TrackQueryHandler queryhandler, String filter,
1007 boolean async) {
1008
1009 if (queryhandler == null) {
1010 throw new IllegalArgumentException();
1011 }
1012
The Android Open Source Project792a2202009-03-03 19:32:30 -08001013 Cursor ret = null;
1014 mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
1015 StringBuilder where = new StringBuilder();
1016 where.append(MediaStore.Audio.Media.TITLE + " != ''");
Marco Nelissen4248ed22009-07-30 08:28:49 -07001017
The Android Open Source Project792a2202009-03-03 19:32:30 -08001018 if (mGenre != null) {
Marco Nelissen89f6c662010-09-02 15:20:23 -07001019 Uri uri = MediaStore.Audio.Genres.Members.getContentUri("external",
1020 Integer.valueOf(mGenre));
1021 if (!TextUtils.isEmpty(filter)) {
1022 uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
1023 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001024 mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
Marco Nelissen89f6c662010-09-02 15:20:23 -07001025 ret = queryhandler.doQuery(uri,
1026 mCursorCols, where.toString(), null, mSortOrder, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001027 } else if (mPlaylist != null) {
1028 if (mPlaylist.equals("nowplaying")) {
1029 if (MusicUtils.sService != null) {
1030 ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
1031 if (ret.getCount() == 0) {
1032 finish();
1033 }
1034 } else {
1035 // Nothing is playing.
1036 }
1037 } else if (mPlaylist.equals("podcasts")) {
1038 where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1");
Marco Nelissen89f6c662010-09-02 15:20:23 -07001039 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
1040 if (!TextUtils.isEmpty(filter)) {
1041 uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
1042 }
1043 ret = queryhandler.doQuery(uri,
1044 mCursorCols, where.toString(), null,
Marco Nelissen4248ed22009-07-30 08:28:49 -07001045 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001046 } else if (mPlaylist.equals("recentlyadded")) {
1047 // do a query for all songs added in the last X weeks
Marco Nelissen89f6c662010-09-02 15:20:23 -07001048 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
1049 if (!TextUtils.isEmpty(filter)) {
1050 uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
1051 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001052 int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
1053 where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">");
1054 where.append(System.currentTimeMillis() / 1000 - X);
Marco Nelissen89f6c662010-09-02 15:20:23 -07001055 ret = queryhandler.doQuery(uri,
1056 mCursorCols, where.toString(), null,
Marco Nelissen4248ed22009-07-30 08:28:49 -07001057 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001058 } else {
Marco Nelissen89f6c662010-09-02 15:20:23 -07001059 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
1060 Long.valueOf(mPlaylist));
1061 if (!TextUtils.isEmpty(filter)) {
1062 uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
1063 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001064 mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
Marco Nelissen89f6c662010-09-02 15:20:23 -07001065 ret = queryhandler.doQuery(uri, mPlaylistMemberCols,
1066 where.toString(), null, mSortOrder, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001067 }
1068 } else {
1069 if (mAlbumId != null) {
1070 where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId);
1071 mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder;
1072 }
1073 if (mArtistId != null) {
1074 where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId);
1075 }
1076 where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
Marco Nelissen89f6c662010-09-02 15:20:23 -07001077 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
1078 if (!TextUtils.isEmpty(filter)) {
1079 uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
1080 }
1081 ret = queryhandler.doQuery(uri,
1082 mCursorCols, where.toString() , null, mSortOrder, async);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001083 }
1084
1085 // This special case is for the "nowplaying" cursor, which cannot be handled
1086 // asynchronously using AsyncQueryHandler, so we do some extra initialization here.
Marco Nelissen4248ed22009-07-30 08:28:49 -07001087 if (ret != null && async) {
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001088 init(ret, false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001089 setTitle();
1090 }
1091 return ret;
1092 }
1093
1094 private class NowPlayingCursor extends AbstractCursor
1095 {
1096 public NowPlayingCursor(IMediaPlaybackService service, String [] cols)
1097 {
1098 mCols = cols;
1099 mService = service;
1100 makeNowPlayingCursor();
1101 }
1102 private void makeNowPlayingCursor() {
1103 mCurrentPlaylistCursor = null;
1104 try {
1105 mNowPlaying = mService.getQueue();
1106 } catch (RemoteException ex) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001107 mNowPlaying = new long[0];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001108 }
1109 mSize = mNowPlaying.length;
1110 if (mSize == 0) {
1111 return;
1112 }
1113
1114 StringBuilder where = new StringBuilder();
1115 where.append(MediaStore.Audio.Media._ID + " IN (");
1116 for (int i = 0; i < mSize; i++) {
1117 where.append(mNowPlaying[i]);
1118 if (i < mSize - 1) {
1119 where.append(",");
1120 }
1121 }
1122 where.append(")");
1123
1124 mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this,
1125 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1126 mCols, where.toString(), null, MediaStore.Audio.Media._ID);
1127
1128 if (mCurrentPlaylistCursor == null) {
1129 mSize = 0;
1130 return;
1131 }
1132
1133 int size = mCurrentPlaylistCursor.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001134 mCursorIdxs = new long[size];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001135 mCurrentPlaylistCursor.moveToFirst();
1136 int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
1137 for (int i = 0; i < size; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001138 mCursorIdxs[i] = mCurrentPlaylistCursor.getLong(colidx);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001139 mCurrentPlaylistCursor.moveToNext();
1140 }
1141 mCurrentPlaylistCursor.moveToFirst();
1142 mCurPos = -1;
1143
1144 // At this point we can verify the 'now playing' list we got
1145 // earlier to make sure that all the items in there still exist
1146 // in the database, and remove those that aren't. This way we
1147 // don't get any blank items in the list.
1148 try {
1149 int removed = 0;
1150 for (int i = mNowPlaying.length - 1; i >= 0; i--) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001151 long trackid = mNowPlaying[i];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001152 int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);
1153 if (crsridx < 0) {
1154 //Log.i("@@@@@", "item no longer exists in db: " + trackid);
1155 removed += mService.removeTrack(trackid);
1156 }
1157 }
1158 if (removed > 0) {
1159 mNowPlaying = mService.getQueue();
1160 mSize = mNowPlaying.length;
1161 if (mSize == 0) {
1162 mCursorIdxs = null;
1163 return;
1164 }
1165 }
1166 } catch (RemoteException ex) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001167 mNowPlaying = new long[0];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001168 }
1169 }
1170
1171 @Override
1172 public int getCount()
1173 {
1174 return mSize;
1175 }
1176
1177 @Override
1178 public boolean onMove(int oldPosition, int newPosition)
1179 {
1180 if (oldPosition == newPosition)
1181 return true;
1182
Marco Nelissenea936712010-02-01 17:01:22 -08001183 if (mNowPlaying == null || mCursorIdxs == null || newPosition >= mNowPlaying.length) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001184 return false;
1185 }
1186
1187 // The cursor doesn't have any duplicates in it, and is not ordered
1188 // in queue-order, so we need to figure out where in the cursor we
1189 // should be.
1190
Marco Nelissenbd447b62009-06-29 14:52:05 -07001191 long newid = mNowPlaying[newPosition];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001192 int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
1193 mCurrentPlaylistCursor.moveToPosition(crsridx);
1194 mCurPos = newPosition;
1195
1196 return true;
1197 }
1198
1199 public boolean removeItem(int which)
1200 {
1201 try {
1202 if (mService.removeTracks(which, which) == 0) {
1203 return false; // delete failed
1204 }
1205 int i = (int) which;
1206 mSize--;
1207 while (i < mSize) {
1208 mNowPlaying[i] = mNowPlaying[i+1];
1209 i++;
1210 }
1211 onMove(-1, (int) mCurPos);
1212 } catch (RemoteException ex) {
1213 }
1214 return true;
1215 }
1216
1217 public void moveItem(int from, int to) {
1218 try {
1219 mService.moveQueueItem(from, to);
1220 mNowPlaying = mService.getQueue();
1221 onMove(-1, mCurPos); // update the underlying cursor
1222 } catch (RemoteException ex) {
1223 }
1224 }
1225
1226 private void dump() {
1227 String where = "(";
1228 for (int i = 0; i < mSize; i++) {
1229 where += mNowPlaying[i];
1230 if (i < mSize - 1) {
1231 where += ",";
1232 }
1233 }
1234 where += ")";
1235 Log.i("NowPlayingCursor: ", where);
1236 }
1237
1238 @Override
1239 public String getString(int column)
1240 {
1241 try {
1242 return mCurrentPlaylistCursor.getString(column);
1243 } catch (Exception ex) {
1244 onChange(true);
1245 return "";
1246 }
1247 }
1248
1249 @Override
1250 public short getShort(int column)
1251 {
1252 return mCurrentPlaylistCursor.getShort(column);
1253 }
1254
1255 @Override
1256 public int getInt(int column)
1257 {
1258 try {
1259 return mCurrentPlaylistCursor.getInt(column);
1260 } catch (Exception ex) {
1261 onChange(true);
1262 return 0;
1263 }
1264 }
1265
1266 @Override
1267 public long getLong(int column)
1268 {
1269 try {
1270 return mCurrentPlaylistCursor.getLong(column);
1271 } catch (Exception ex) {
1272 onChange(true);
1273 return 0;
1274 }
1275 }
1276
1277 @Override
1278 public float getFloat(int column)
1279 {
1280 return mCurrentPlaylistCursor.getFloat(column);
1281 }
1282
1283 @Override
1284 public double getDouble(int column)
1285 {
1286 return mCurrentPlaylistCursor.getDouble(column);
1287 }
1288
1289 @Override
1290 public boolean isNull(int column)
1291 {
1292 return mCurrentPlaylistCursor.isNull(column);
1293 }
1294
1295 @Override
1296 public String[] getColumnNames()
1297 {
1298 return mCols;
1299 }
1300
1301 @Override
1302 public void deactivate()
1303 {
1304 if (mCurrentPlaylistCursor != null)
1305 mCurrentPlaylistCursor.deactivate();
1306 }
1307
1308 @Override
1309 public boolean requery()
1310 {
1311 makeNowPlayingCursor();
1312 return true;
1313 }
1314
1315 private String [] mCols;
1316 private Cursor mCurrentPlaylistCursor; // updated in onMove
1317 private int mSize; // size of the queue
Marco Nelissenbd447b62009-06-29 14:52:05 -07001318 private long[] mNowPlaying;
1319 private long[] mCursorIdxs;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001320 private int mCurPos;
1321 private IMediaPlaybackService mService;
1322 }
1323
1324 static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer {
1325 boolean mIsNowPlaying;
1326 boolean mDisableNowPlayingIndicator;
1327
1328 int mTitleIdx;
1329 int mArtistIdx;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001330 int mDurationIdx;
1331 int mAudioIdIdx;
1332
1333 private final StringBuilder mBuilder = new StringBuilder();
1334 private final String mUnknownArtist;
1335 private final String mUnknownAlbum;
1336
1337 private AlphabetIndexer mIndexer;
1338
1339 private TrackBrowserActivity mActivity = null;
Marco Nelissen4248ed22009-07-30 08:28:49 -07001340 private TrackQueryHandler mQueryHandler;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001341 private String mConstraint = null;
1342 private boolean mConstraintIsValid = false;
1343
Marco Nelissen756c3f52009-05-14 10:07:23 -07001344 static class ViewHolder {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001345 TextView line1;
1346 TextView line2;
1347 TextView duration;
1348 ImageView play_indicator;
1349 CharArrayBuffer buffer1;
1350 char [] buffer2;
1351 }
1352
Marco Nelissen4248ed22009-07-30 08:28:49 -07001353 class TrackQueryHandler extends AsyncQueryHandler {
Marco Nelissene7887042009-07-30 10:40:49 -07001354
1355 class QueryArgs {
1356 public Uri uri;
1357 public String [] projection;
1358 public String selection;
1359 public String [] selectionArgs;
1360 public String orderBy;
1361 }
1362
Marco Nelissen4248ed22009-07-30 08:28:49 -07001363 TrackQueryHandler(ContentResolver res) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001364 super(res);
1365 }
1366
Marco Nelissen4248ed22009-07-30 08:28:49 -07001367 public Cursor doQuery(Uri uri, String[] projection,
1368 String selection, String[] selectionArgs,
1369 String orderBy, boolean async) {
1370 if (async) {
Marco Nelissene7887042009-07-30 10:40:49 -07001371 // Get 100 results first, which is enough to allow the user to start scrolling,
1372 // while still being very fast.
1373 Uri limituri = uri.buildUpon().appendQueryParameter("limit", "100").build();
1374 QueryArgs args = new QueryArgs();
1375 args.uri = uri;
1376 args.projection = projection;
1377 args.selection = selection;
1378 args.selectionArgs = selectionArgs;
1379 args.orderBy = orderBy;
1380
1381 startQuery(0, args, limituri, projection, selection, selectionArgs, orderBy);
Marco Nelissen4248ed22009-07-30 08:28:49 -07001382 return null;
1383 }
1384 return MusicUtils.query(mActivity,
1385 uri, projection, selection, selectionArgs, orderBy);
1386 }
1387
The Android Open Source Project792a2202009-03-03 19:32:30 -08001388 @Override
1389 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
1390 //Log.i("@@@", "query complete: " + cursor.getCount() + " " + mActivity);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001391 mActivity.init(cursor, cookie != null);
Marco Nelissene7887042009-07-30 10:40:49 -07001392 if (token == 0 && cookie != null && cursor != null && cursor.getCount() >= 100) {
1393 QueryArgs args = (QueryArgs) cookie;
1394 startQuery(1, null, args.uri, args.projection, args.selection,
1395 args.selectionArgs, args.orderBy);
1396 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001397 }
1398 }
1399
1400 TrackListAdapter(Context context, TrackBrowserActivity currentactivity,
1401 int layout, Cursor cursor, String[] from, int[] to,
1402 boolean isnowplaying, boolean disablenowplayingindicator) {
1403 super(context, layout, cursor, from, to);
1404 mActivity = currentactivity;
1405 getColumnIndices(cursor);
1406 mIsNowPlaying = isnowplaying;
1407 mDisableNowPlayingIndicator = disablenowplayingindicator;
1408 mUnknownArtist = context.getString(R.string.unknown_artist_name);
1409 mUnknownAlbum = context.getString(R.string.unknown_album_name);
1410
Marco Nelissen4248ed22009-07-30 08:28:49 -07001411 mQueryHandler = new TrackQueryHandler(context.getContentResolver());
The Android Open Source Project792a2202009-03-03 19:32:30 -08001412 }
1413
1414 public void setActivity(TrackBrowserActivity newactivity) {
1415 mActivity = newactivity;
1416 }
1417
Marco Nelissen4248ed22009-07-30 08:28:49 -07001418 public TrackQueryHandler getQueryHandler() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001419 return mQueryHandler;
1420 }
1421
1422 private void getColumnIndices(Cursor cursor) {
1423 if (cursor != null) {
1424 mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
1425 mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001426 mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION);
1427 try {
1428 mAudioIdIdx = cursor.getColumnIndexOrThrow(
1429 MediaStore.Audio.Playlists.Members.AUDIO_ID);
1430 } catch (IllegalArgumentException ex) {
1431 mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
1432 }
1433
1434 if (mIndexer != null) {
1435 mIndexer.setCursor(cursor);
Marco Nelissen83a2e722010-09-17 09:39:54 -07001436 } else if (!mActivity.mEditMode && mActivity.mAlbumId == null) {
Marco Nelissen3d4b2622010-01-06 12:46:49 -08001437 String alpha = mActivity.getString(R.string.fast_scroll_alphabet);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001438
1439 mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha);
1440 }
1441 }
1442 }
1443
1444 @Override
1445 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1446 View v = super.newView(context, cursor, parent);
1447 ImageView iv = (ImageView) v.findViewById(R.id.icon);
Marco Nelissen355e1342010-07-30 17:34:03 -07001448 iv.setVisibility(View.GONE);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001449
1450 ViewHolder vh = new ViewHolder();
1451 vh.line1 = (TextView) v.findViewById(R.id.line1);
1452 vh.line2 = (TextView) v.findViewById(R.id.line2);
1453 vh.duration = (TextView) v.findViewById(R.id.duration);
1454 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
1455 vh.buffer1 = new CharArrayBuffer(100);
1456 vh.buffer2 = new char[200];
1457 v.setTag(vh);
1458 return v;
1459 }
1460
1461 @Override
1462 public void bindView(View view, Context context, Cursor cursor) {
1463
1464 ViewHolder vh = (ViewHolder) view.getTag();
1465
1466 cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
1467 vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
1468
1469 int secs = cursor.getInt(mDurationIdx) / 1000;
1470 if (secs == 0) {
1471 vh.duration.setText("");
1472 } else {
1473 vh.duration.setText(MusicUtils.makeTimeString(context, secs));
1474 }
1475
1476 final StringBuilder builder = mBuilder;
1477 builder.delete(0, builder.length());
1478
1479 String name = cursor.getString(mArtistIdx);
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001480 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001481 builder.append(mUnknownArtist);
1482 } else {
1483 builder.append(name);
1484 }
1485 int len = builder.length();
1486 if (vh.buffer2.length < len) {
1487 vh.buffer2 = new char[len];
1488 }
1489 builder.getChars(0, len, vh.buffer2, 0);
1490 vh.line2.setText(vh.buffer2, 0, len);
1491
1492 ImageView iv = vh.play_indicator;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001493 long id = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001494 if (MusicUtils.sService != null) {
1495 // TODO: IPC call on each bind??
1496 try {
1497 if (mIsNowPlaying) {
1498 id = MusicUtils.sService.getQueuePosition();
1499 } else {
1500 id = MusicUtils.sService.getAudioId();
1501 }
1502 } catch (RemoteException ex) {
1503 }
1504 }
1505
1506 // Determining whether and where to show the "now playing indicator
1507 // is tricky, because we don't actually keep track of where the songs
1508 // in the current playlist came from after they've started playing.
1509 //
1510 // If the "current playlists" is shown, then we can simply match by position,
1511 // otherwise, we need to match by id. Match-by-id gets a little weird if
1512 // a song appears in a playlist more than once, and you're in edit-playlist
1513 // mode. In that case, both items will have the "now playing" indicator.
1514 // For this reason, we don't show the play indicator at all when in edit
1515 // playlist mode (except when you're viewing the "current playlist",
1516 // which is not really a playlist)
1517 if ( (mIsNowPlaying && cursor.getPosition() == id) ||
Marco Nelissenbd447b62009-06-29 14:52:05 -07001518 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getLong(mAudioIdIdx) == id)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001519 iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
1520 iv.setVisibility(View.VISIBLE);
1521 } else {
1522 iv.setVisibility(View.GONE);
1523 }
1524 }
1525
1526 @Override
1527 public void changeCursor(Cursor cursor) {
Marco Nelissend99bc1e2009-12-04 08:48:06 -08001528 if (mActivity.isFinishing() && cursor != null) {
1529 cursor.close();
1530 cursor = null;
Marco Nelissen0e175782009-12-03 14:24:53 -08001531 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001532 if (cursor != mActivity.mTrackCursor) {
1533 mActivity.mTrackCursor = cursor;
1534 super.changeCursor(cursor);
1535 getColumnIndices(cursor);
1536 }
1537 }
1538
1539 @Override
1540 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1541 String s = constraint.toString();
1542 if (mConstraintIsValid && (
1543 (s == null && mConstraint == null) ||
1544 (s != null && s.equals(mConstraint)))) {
1545 return getCursor();
1546 }
Marco Nelissen4248ed22009-07-30 08:28:49 -07001547 Cursor c = mActivity.getTrackCursor(mQueryHandler, s, false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001548 mConstraint = s;
1549 mConstraintIsValid = true;
1550 return c;
1551 }
1552
1553 // SectionIndexer methods
1554
1555 public Object[] getSections() {
1556 if (mIndexer != null) {
1557 return mIndexer.getSections();
1558 } else {
1559 return null;
1560 }
1561 }
1562
1563 public int getPositionForSection(int section) {
1564 int pos = mIndexer.getPositionForSection(section);
1565 return pos;
1566 }
1567
1568 public int getSectionForPosition(int position) {
1569 return 0;
1570 }
1571 }
1572}
1573