1 /* 2 * Copyright (C) 2008 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 17 package com.android.music; 18 19 import android.app.Activity; 20 import android.content.ComponentName; 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.ContextWrapper; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.content.SharedPreferences; 29 import android.content.SharedPreferences.Editor; 30 import android.content.res.Resources; 31 import android.database.Cursor; 32 import android.graphics.Bitmap; 33 import android.graphics.BitmapFactory; 34 import android.graphics.Canvas; 35 import android.graphics.ColorFilter; 36 import android.graphics.ColorMatrix; 37 import android.graphics.ColorMatrixColorFilter; 38 import android.graphics.Matrix; 39 import android.graphics.Paint; 40 import android.graphics.PixelFormat; 41 import android.graphics.drawable.BitmapDrawable; 42 import android.graphics.drawable.Drawable; 43 import android.net.Uri; 44 import android.os.Environment; 45 import android.os.ParcelFileDescriptor; 46 import android.os.RemoteException; 47 import android.provider.MediaStore; 48 import android.provider.Settings; 49 import android.text.TextUtils; 50 import android.text.format.Time; 51 import android.util.Log; 52 import android.view.Menu; 53 import android.view.MenuItem; 54 import android.view.SubMenu; 55 import android.view.View; 56 import android.view.Window; 57 import android.widget.TabWidget; 58 import android.widget.TextView; 59 import android.widget.Toast; 60 61 import java.io.File; 62 import java.io.FileDescriptor; 63 import java.io.FileNotFoundException; 64 import java.io.IOException; 65 import java.io.InputStream; 66 import java.io.PrintWriter; 67 import java.util.Arrays; 68 import java.util.Formatter; 69 import java.util.HashMap; 70 import java.util.Locale; 71 72 public class MusicUtils { 73 private static final String TAG = "MusicUtils"; 74 75 public interface Defs { 76 public final static int OPEN_URL = 0; 77 public final static int ADD_TO_PLAYLIST = 1; 78 public final static int USE_AS_RINGTONE = 2; 79 public final static int PLAYLIST_SELECTED = 3; 80 public final static int NEW_PLAYLIST = 4; 81 public final static int PLAY_SELECTION = 5; 82 public final static int GOTO_START = 6; 83 public final static int GOTO_PLAYBACK = 7; 84 public final static int PARTY_SHUFFLE = 8; 85 public final static int SHUFFLE_ALL = 9; 86 public final static int DELETE_ITEM = 10; 87 public final static int SCAN_DONE = 11; 88 public final static int QUEUE = 12; 89 public final static int EFFECTS_PANEL = 13; 90 public final static int CHILD_MENU_BASE = 14; // this should be the last item 91 } 92 makeAlbumsLabel( Context context, int numalbums, int numsongs, boolean isUnknown)93 public static String makeAlbumsLabel( 94 Context context, int numalbums, int numsongs, boolean isUnknown) { 95 // There are two formats for the albums/songs information: 96 // "N Song(s)" - used for unknown artist/album 97 // "N Album(s)" - used for known albums 98 99 StringBuilder songs_albums = new StringBuilder(); 100 101 Resources r = context.getResources(); 102 if (isUnknown) { 103 if (numsongs == 1) { 104 songs_albums.append(context.getString(R.string.onesong)); 105 } else { 106 String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString(); 107 sFormatBuilder.setLength(0); 108 sFormatter.format(f, Integer.valueOf(numsongs)); 109 songs_albums.append(sFormatBuilder); 110 } 111 } else { 112 String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString(); 113 sFormatBuilder.setLength(0); 114 sFormatter.format(f, Integer.valueOf(numalbums)); 115 songs_albums.append(sFormatBuilder); 116 songs_albums.append(context.getString(R.string.albumsongseparator)); 117 } 118 return songs_albums.toString(); 119 } 120 121 /** 122 * This is now only used for the query screen 123 */ makeAlbumsSongsLabel( Context context, int numalbums, int numsongs, boolean isUnknown)124 public static String makeAlbumsSongsLabel( 125 Context context, int numalbums, int numsongs, boolean isUnknown) { 126 // There are several formats for the albums/songs information: 127 // "1 Song" - used if there is only 1 song 128 // "N Songs" - used for the "unknown artist" item 129 // "1 Album"/"N Songs" 130 // "N Album"/"M Songs" 131 // Depending on locale, these may need to be further subdivided 132 133 StringBuilder songs_albums = new StringBuilder(); 134 135 if (numsongs == 1) { 136 songs_albums.append(context.getString(R.string.onesong)); 137 } else { 138 Resources r = context.getResources(); 139 if (!isUnknown) { 140 String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString(); 141 sFormatBuilder.setLength(0); 142 sFormatter.format(f, Integer.valueOf(numalbums)); 143 songs_albums.append(sFormatBuilder); 144 songs_albums.append(context.getString(R.string.albumsongseparator)); 145 } 146 String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString(); 147 sFormatBuilder.setLength(0); 148 sFormatter.format(f, Integer.valueOf(numsongs)); 149 songs_albums.append(sFormatBuilder); 150 } 151 return songs_albums.toString(); 152 } 153 154 public static IMediaPlaybackService sService = null; 155 private static HashMap<Context, ServiceBinder> sConnectionMap = 156 new HashMap<Context, ServiceBinder>(); 157 158 public static class ServiceToken { 159 ContextWrapper mWrappedContext; ServiceToken(ContextWrapper context)160 ServiceToken(ContextWrapper context) { 161 mWrappedContext = context; 162 } 163 } 164 bindToService(Activity context)165 public static ServiceToken bindToService(Activity context) { 166 return bindToService(context, null); 167 } 168 bindToService(Activity context, ServiceConnection callback)169 public static ServiceToken bindToService(Activity context, ServiceConnection callback) { 170 Activity realActivity = context.getParent(); 171 if (realActivity == null) { 172 realActivity = context; 173 } 174 ContextWrapper cw = new ContextWrapper(realActivity); 175 cw.startService(new Intent(cw, MediaPlaybackService.class)); 176 ServiceBinder sb = new ServiceBinder(callback); 177 if (cw.bindService((new Intent()).setClass(cw, MediaPlaybackService.class), sb, 0)) { 178 sConnectionMap.put(cw, sb); 179 return new ServiceToken(cw); 180 } 181 Log.e("Music", "Failed to bind to service"); 182 return null; 183 } 184 unbindFromService(ServiceToken token)185 public static void unbindFromService(ServiceToken token) { 186 if (token == null) { 187 Log.e("MusicUtils", "Trying to unbind with null token"); 188 return; 189 } 190 ContextWrapper cw = token.mWrappedContext; 191 ServiceBinder sb = sConnectionMap.remove(cw); 192 if (sb == null) { 193 Log.e("MusicUtils", "Trying to unbind for unknown Context"); 194 return; 195 } 196 cw.unbindService(sb); 197 if (sConnectionMap.isEmpty()) { 198 // presumably there is nobody interested in the service at this point, 199 // so don't hang on to the ServiceConnection 200 sService = null; 201 } 202 } 203 204 private static class ServiceBinder implements ServiceConnection { 205 ServiceConnection mCallback; ServiceBinder(ServiceConnection callback)206 ServiceBinder(ServiceConnection callback) { 207 mCallback = callback; 208 } 209 onServiceConnected(ComponentName className, android.os.IBinder service)210 public void onServiceConnected(ComponentName className, android.os.IBinder service) { 211 sService = IMediaPlaybackService.Stub.asInterface(service); 212 initAlbumArtCache(); 213 if (mCallback != null) { 214 mCallback.onServiceConnected(className, service); 215 } 216 } 217 onServiceDisconnected(ComponentName className)218 public void onServiceDisconnected(ComponentName className) { 219 if (mCallback != null) { 220 mCallback.onServiceDisconnected(className); 221 } 222 sService = null; 223 } 224 } 225 getCurrentAlbumId()226 public static long getCurrentAlbumId() { 227 if (sService != null) { 228 try { 229 return sService.getAlbumId(); 230 } catch (RemoteException ex) { 231 } 232 } 233 return -1; 234 } 235 getCurrentArtistId()236 public static long getCurrentArtistId() { 237 if (MusicUtils.sService != null) { 238 try { 239 return sService.getArtistId(); 240 } catch (RemoteException ex) { 241 } 242 } 243 return -1; 244 } 245 getCurrentAudioId()246 public static long getCurrentAudioId() { 247 if (MusicUtils.sService != null) { 248 try { 249 return sService.getAudioId(); 250 } catch (RemoteException ex) { 251 } 252 } 253 return -1; 254 } 255 getCurrentShuffleMode()256 public static int getCurrentShuffleMode() { 257 int mode = MediaPlaybackService.SHUFFLE_NONE; 258 if (sService != null) { 259 try { 260 mode = sService.getShuffleMode(); 261 } catch (RemoteException ex) { 262 } 263 } 264 return mode; 265 } 266 togglePartyShuffle()267 public static void togglePartyShuffle() { 268 if (sService != null) { 269 int shuffle = getCurrentShuffleMode(); 270 try { 271 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) { 272 sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE); 273 } else { 274 sService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO); 275 } 276 } catch (RemoteException ex) { 277 } 278 } 279 } 280 setPartyShuffleMenuIcon(Menu menu)281 public static void setPartyShuffleMenuIcon(Menu menu) { 282 MenuItem item = menu.findItem(Defs.PARTY_SHUFFLE); 283 if (item != null) { 284 int shuffle = MusicUtils.getCurrentShuffleMode(); 285 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) { 286 item.setIcon(R.drawable.ic_menu_party_shuffle); 287 item.setTitle(R.string.party_shuffle_off); 288 } else { 289 item.setIcon(R.drawable.ic_menu_party_shuffle); 290 item.setTitle(R.string.party_shuffle); 291 } 292 } 293 } 294 295 /* 296 * Returns true if a file is currently opened for playback (regardless 297 * of whether it's playing or paused). 298 */ isMusicLoaded()299 public static boolean isMusicLoaded() { 300 if (MusicUtils.sService != null) { 301 try { 302 return sService.getPath() != null; 303 } catch (RemoteException ex) { 304 } 305 } 306 return false; 307 } 308 309 private final static long[] sEmptyList = new long[0]; 310 getSongListForCursor(Cursor cursor)311 public static long[] getSongListForCursor(Cursor cursor) { 312 if (cursor == null) { 313 return sEmptyList; 314 } 315 int len = cursor.getCount(); 316 long[] list = new long[len]; 317 cursor.moveToFirst(); 318 int colidx = -1; 319 try { 320 colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID); 321 } catch (IllegalArgumentException ex) { 322 colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); 323 } 324 for (int i = 0; i < len; i++) { 325 list[i] = cursor.getLong(colidx); 326 cursor.moveToNext(); 327 } 328 return list; 329 } 330 getSongListForArtist(Context context, long id)331 public static long[] getSongListForArtist(Context context, long id) { 332 final String[] ccols = new String[] {MediaStore.Audio.Media._ID}; 333 String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND " 334 + MediaStore.Audio.Media.IS_MUSIC + "=1"; 335 Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where, 336 null, MediaStore.Audio.Media.ALBUM_KEY + "," + MediaStore.Audio.Media.TRACK); 337 338 if (cursor != null) { 339 long[] list = getSongListForCursor(cursor); 340 cursor.close(); 341 return list; 342 } 343 return sEmptyList; 344 } 345 getSongListForAlbum(Context context, long id)346 public static long[] getSongListForAlbum(Context context, long id) { 347 final String[] ccols = new String[] {MediaStore.Audio.Media._ID}; 348 String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND " 349 + MediaStore.Audio.Media.IS_MUSIC + "=1"; 350 Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where, 351 null, MediaStore.Audio.Media.TRACK); 352 353 if (cursor != null) { 354 long[] list = getSongListForCursor(cursor); 355 cursor.close(); 356 return list; 357 } 358 return sEmptyList; 359 } 360 getSongListForPlaylist(Context context, long plid)361 public static long[] getSongListForPlaylist(Context context, long plid) { 362 final String[] ccols = new String[] {MediaStore.Audio.Playlists.Members.AUDIO_ID}; 363 Cursor cursor = 364 query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid), 365 ccols, null, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); 366 367 if (cursor != null) { 368 long[] list = getSongListForCursor(cursor); 369 cursor.close(); 370 return list; 371 } 372 return sEmptyList; 373 } 374 playPlaylist(Context context, long plid)375 public static void playPlaylist(Context context, long plid) { 376 long[] list = getSongListForPlaylist(context, plid); 377 if (list != null) { 378 playAll(context, list, -1, false); 379 } 380 } 381 getAllSongs(Context context)382 public static long[] getAllSongs(Context context) { 383 Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 384 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1", 385 null, null); 386 try { 387 if (c == null || c.getCount() == 0) { 388 return null; 389 } 390 int len = c.getCount(); 391 long[] list = new long[len]; 392 for (int i = 0; i < len; i++) { 393 c.moveToNext(); 394 list[i] = c.getLong(0); 395 } 396 397 return list; 398 } finally { 399 if (c != null) { 400 c.close(); 401 } 402 } 403 } 404 405 /** 406 * Fills out the given submenu with items for "new playlist" and 407 * any existing playlists. When the user selects an item, the 408 * application will receive PLAYLIST_SELECTED with the Uri of 409 * the selected playlist, NEW_PLAYLIST if a new playlist 410 * should be created, and QUEUE if the "current playlist" was 411 * selected. 412 * @param context The context to use for creating the menu items 413 * @param sub The submenu to add the items to. 414 */ makePlaylistMenu(Context context, SubMenu sub)415 public static void makePlaylistMenu(Context context, SubMenu sub) { 416 String[] cols = 417 new String[] {MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME}; 418 ContentResolver resolver = context.getContentResolver(); 419 if (resolver == null) { 420 System.out.println("resolver = null"); 421 } else { 422 String whereclause = MediaStore.Audio.Playlists.NAME + " != ''"; 423 Cursor cur = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, cols, 424 whereclause, null, MediaStore.Audio.Playlists.NAME); 425 sub.clear(); 426 sub.add(1, Defs.QUEUE, 0, R.string.queue); 427 sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist); 428 if (cur != null && cur.getCount() > 0) { 429 // sub.addSeparator(1, 0); 430 cur.moveToFirst(); 431 while (!cur.isAfterLast()) { 432 Intent intent = new Intent(); 433 intent.putExtra("playlist", cur.getLong(0)); 434 // if (cur.getInt(0) == mLastPlaylistSelected) { 435 // sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED, 436 // cur.getString(1)).setIntent(intent); 437 // } else { 438 sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1)).setIntent(intent); 439 // } 440 cur.moveToNext(); 441 } 442 } 443 if (cur != null) { 444 cur.close(); 445 } 446 } 447 } 448 clearPlaylist(Context context, int plid)449 public static void clearPlaylist(Context context, int plid) { 450 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", plid); 451 context.getContentResolver().delete(uri, null, null); 452 return; 453 } 454 deleteTracks(Context context, long[] list)455 public static void deleteTracks(Context context, long[] list) { 456 String[] cols = new String[] {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA, 457 MediaStore.Audio.Media.ALBUM_ID}; 458 StringBuilder where = new StringBuilder(); 459 where.append(MediaStore.Audio.Media._ID + " IN ("); 460 for (int i = 0; i < list.length; i++) { 461 where.append(list[i]); 462 if (i < list.length - 1) { 463 where.append(","); 464 } 465 } 466 where.append(")"); 467 Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, 468 where.toString(), null, null); 469 470 if (c != null) { 471 // step 1: remove selected tracks from the current playlist, as well 472 // as from the album art cache 473 try { 474 c.moveToFirst(); 475 while (!c.isAfterLast()) { 476 // remove from current playlist 477 long id = c.getLong(0); 478 sService.removeTrack(id); 479 // remove from album art cache 480 long artIndex = c.getLong(2); 481 synchronized (sArtCache) { 482 sArtCache.remove(artIndex); 483 } 484 c.moveToNext(); 485 } 486 } catch (RemoteException ex) { 487 } 488 489 // step 2: remove selected tracks from the database 490 context.getContentResolver().delete( 491 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where.toString(), null); 492 493 // step 3: remove files from card 494 c.moveToFirst(); 495 while (!c.isAfterLast()) { 496 String name = c.getString(1); 497 File f = new File(name); 498 try { // File.delete can throw a security exception 499 if (!f.delete()) { 500 // I'm not sure if we'd ever get here (deletion would 501 // have to fail, but no exception thrown) 502 Log.e("MusicUtils", "Failed to delete file " + name); 503 } 504 c.moveToNext(); 505 } catch (SecurityException ex) { 506 c.moveToNext(); 507 } 508 } 509 c.close(); 510 } 511 512 String message = context.getResources().getQuantityString( 513 R.plurals.NNNtracksdeleted, list.length, Integer.valueOf(list.length)); 514 515 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 516 // We deleted a number of tracks, which could affect any number of things 517 // in the media content domain, so update everything. 518 context.getContentResolver().notifyChange(Uri.parse("content://media"), null); 519 } 520 addToCurrentPlaylist(Context context, long[] list)521 public static void addToCurrentPlaylist(Context context, long[] list) { 522 if (sService == null) { 523 return; 524 } 525 try { 526 sService.enqueue(list, MediaPlaybackService.LAST); 527 String message = context.getResources().getQuantityString( 528 R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length)); 529 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 530 } catch (RemoteException ex) { 531 } 532 } 533 534 private static ContentValues[] sContentValuesCache = null; 535 536 /** 537 * @param ids The source array containing all the ids to be added to the playlist 538 * @param offset Where in the 'ids' array we start reading 539 * @param len How many items to copy during this pass 540 * @param base The play order offset to use for this pass 541 */ makeInsertItems(long[] ids, int offset, int len, int base)542 private static void makeInsertItems(long[] ids, int offset, int len, int base) { 543 // adjust 'len' if would extend beyond the end of the source array 544 if (offset + len > ids.length) { 545 len = ids.length - offset; 546 } 547 // allocate the ContentValues array, or reallocate if it is the wrong size 548 if (sContentValuesCache == null || sContentValuesCache.length != len) { 549 sContentValuesCache = new ContentValues[len]; 550 } 551 // fill in the ContentValues array with the right values for this pass 552 for (int i = 0; i < len; i++) { 553 if (sContentValuesCache[i] == null) { 554 sContentValuesCache[i] = new ContentValues(); 555 } 556 557 sContentValuesCache[i].put( 558 MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i); 559 sContentValuesCache[i].put( 560 MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]); 561 } 562 } 563 addToPlaylist(Context context, long[] ids, long playlistid)564 public static void addToPlaylist(Context context, long[] ids, long playlistid) { 565 if (ids == null) { 566 // this shouldn't happen (the menuitems shouldn't be visible 567 // unless the selected item represents something playable 568 Log.e("MusicBase", "ListSelection null"); 569 } else { 570 int size = ids.length; 571 ContentResolver resolver = context.getContentResolver(); 572 // need to determine the number of items currently in the playlist, 573 // so the play_order field can be maintained. 574 String[] cols = new String[] {"count(*)"}; 575 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid); 576 Cursor cur = resolver.query(uri, cols, null, null, null); 577 cur.moveToFirst(); 578 int base = cur.getInt(0); 579 cur.close(); 580 int numinserted = 0; 581 for (int i = 0; i < size; i += 1000) { 582 makeInsertItems(ids, i, 1000, base); 583 numinserted += resolver.bulkInsert(uri, sContentValuesCache); 584 } 585 String message = context.getResources().getQuantityString( 586 R.plurals.NNNtrackstoplaylist, numinserted, numinserted); 587 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 588 // mLastPlaylistSelected = playlistid; 589 } 590 } 591 query(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, int limit)592 public static Cursor query(Context context, Uri uri, String[] projection, String selection, 593 String[] selectionArgs, String sortOrder, int limit) { 594 try { 595 ContentResolver resolver = context.getContentResolver(); 596 if (resolver == null) { 597 return null; 598 } 599 if (limit > 0) { 600 uri = uri.buildUpon().appendQueryParameter("limit", "" + limit).build(); 601 } 602 return resolver.query(uri, projection, selection, selectionArgs, sortOrder); 603 } catch (UnsupportedOperationException ex) { 604 return null; 605 } 606 } query(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)607 public static Cursor query(Context context, Uri uri, String[] projection, String selection, 608 String[] selectionArgs, String sortOrder) { 609 return query(context, uri, projection, selection, selectionArgs, sortOrder, 0); 610 } 611 isMediaScannerScanning(Context context)612 public static boolean isMediaScannerScanning(Context context) { 613 boolean result = false; 614 Cursor cursor = query(context, MediaStore.getMediaScannerUri(), 615 new String[] {MediaStore.MEDIA_SCANNER_VOLUME}, null, null, null); 616 if (cursor != null) { 617 if (cursor.getCount() == 1) { 618 cursor.moveToFirst(); 619 result = "external".equals(cursor.getString(0)); 620 } 621 cursor.close(); 622 } 623 624 return result; 625 } 626 setSpinnerState(Activity a)627 public static void setSpinnerState(Activity a) { 628 if (isMediaScannerScanning(a)) { 629 // start the progress spinner 630 a.getWindow().setFeatureInt( 631 Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_INDETERMINATE_ON); 632 633 a.getWindow().setFeatureInt( 634 Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_VISIBILITY_ON); 635 } else { 636 // stop the progress spinner 637 a.getWindow().setFeatureInt( 638 Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_VISIBILITY_OFF); 639 } 640 } 641 642 private static String mLastSdStatus; 643 displayDatabaseError(Activity a)644 public static void displayDatabaseError(Activity a) { 645 if (a.isFinishing()) { 646 // When switching tabs really fast, we can end up with a null 647 // cursor (not sure why), which will bring us here. 648 // Don't bother showing an error message in that case. 649 return; 650 } 651 652 String status = Environment.getExternalStorageState(); 653 int title, message; 654 655 if (android.os.Environment.isExternalStorageRemovable()) { 656 title = R.string.sdcard_error_title; 657 message = R.string.sdcard_error_message; 658 } else { 659 title = R.string.sdcard_error_title_nosdcard; 660 message = R.string.sdcard_error_message_nosdcard; 661 } 662 663 if (status.equals(Environment.MEDIA_SHARED) || status.equals(Environment.MEDIA_UNMOUNTED)) { 664 if (android.os.Environment.isExternalStorageRemovable()) { 665 title = R.string.sdcard_busy_title; 666 message = R.string.sdcard_busy_message; 667 } else { 668 title = R.string.sdcard_busy_title_nosdcard; 669 message = R.string.sdcard_busy_message_nosdcard; 670 } 671 } else if (status.equals(Environment.MEDIA_REMOVED)) { 672 if (android.os.Environment.isExternalStorageRemovable()) { 673 title = R.string.sdcard_missing_title; 674 message = R.string.sdcard_missing_message; 675 } else { 676 title = R.string.sdcard_missing_title_nosdcard; 677 message = R.string.sdcard_missing_message_nosdcard; 678 } 679 } else if (status.equals(Environment.MEDIA_MOUNTED)) { 680 // The card is mounted, but we didn't get a valid cursor. 681 // This probably means the mediascanner hasn't started scanning the 682 // card yet (there is a small window of time during boot where this 683 // will happen). 684 a.setTitle(""); 685 Intent intent = new Intent(); 686 intent.setClass(a, ScanningProgress.class); 687 a.startActivityForResult(intent, Defs.SCAN_DONE); 688 } else if (!TextUtils.equals(mLastSdStatus, status)) { 689 mLastSdStatus = status; 690 Log.d(TAG, "sd card: " + status); 691 } 692 693 a.setTitle(title); 694 View v = a.findViewById(R.id.sd_message); 695 if (v != null) { 696 v.setVisibility(View.VISIBLE); 697 } 698 v = a.findViewById(R.id.sd_icon); 699 if (v != null) { 700 v.setVisibility(View.VISIBLE); 701 } 702 v = a.findViewById(android.R.id.list); 703 if (v != null) { 704 v.setVisibility(View.GONE); 705 } 706 v = a.findViewById(R.id.buttonbar); 707 if (v != null) { 708 v.setVisibility(View.GONE); 709 } 710 TextView tv = (TextView) a.findViewById(R.id.sd_message); 711 tv.setText(message); 712 } 713 hideDatabaseError(Activity a)714 public static void hideDatabaseError(Activity a) { 715 View v = a.findViewById(R.id.sd_message); 716 if (v != null) { 717 v.setVisibility(View.GONE); 718 } 719 v = a.findViewById(R.id.sd_icon); 720 if (v != null) { 721 v.setVisibility(View.GONE); 722 } 723 v = a.findViewById(android.R.id.list); 724 if (v != null) { 725 v.setVisibility(View.VISIBLE); 726 } 727 } 728 getContentURIForPath(String path)729 static protected Uri getContentURIForPath(String path) { 730 return Uri.fromFile(new File(path)); 731 } 732 733 /* Try to use String.format() as little as possible, because it creates a 734 * new Formatter every time you call it, which is very inefficient. 735 * Reusing an existing Formatter more than tripled the speed of 736 * makeTimeString(). 737 * This Formatter/StringBuilder are also used by makeAlbumSongsLabel() 738 */ 739 private static StringBuilder sFormatBuilder = new StringBuilder(); 740 private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault()); 741 private static final Object[] sTimeArgs = new Object[5]; 742 makeTimeString(Context context, long secs)743 public static String makeTimeString(Context context, long secs) { 744 String durationformat = context.getString( 745 secs < 3600 ? R.string.durationformatshort : R.string.durationformatlong); 746 747 /* Provide multiple arguments so the format can be changed easily 748 * by modifying the xml. 749 */ 750 sFormatBuilder.setLength(0); 751 752 final Object[] timeArgs = sTimeArgs; 753 timeArgs[0] = secs / 3600; 754 timeArgs[1] = secs / 60; 755 timeArgs[2] = (secs / 60) % 60; 756 timeArgs[3] = secs; 757 timeArgs[4] = secs % 60; 758 759 return sFormatter.format(durationformat, timeArgs).toString(); 760 } 761 shuffleAll(Context context, Cursor cursor)762 public static void shuffleAll(Context context, Cursor cursor) { 763 playAll(context, cursor, 0, true); 764 } 765 playAll(Context context, Cursor cursor)766 public static void playAll(Context context, Cursor cursor) { 767 playAll(context, cursor, 0, false); 768 } 769 playAll(Context context, Cursor cursor, int position)770 public static void playAll(Context context, Cursor cursor, int position) { 771 playAll(context, cursor, position, false); 772 } 773 playAll(Context context, long[] list, int position)774 public static void playAll(Context context, long[] list, int position) { 775 playAll(context, list, position, false); 776 } 777 playAll( Context context, Cursor cursor, int position, boolean force_shuffle)778 private static void playAll( 779 Context context, Cursor cursor, int position, boolean force_shuffle) { 780 long[] list = getSongListForCursor(cursor); 781 playAll(context, list, position, force_shuffle); 782 } 783 playAll(Context context, long[] list, int position, boolean force_shuffle)784 private static void playAll(Context context, long[] list, int position, boolean force_shuffle) { 785 if (list.length == 0 || sService == null) { 786 Log.d("MusicUtils", "attempt to play empty song list"); 787 // Don't try to play empty playlists. Nothing good will come of it. 788 String message = context.getString(R.string.emptyplaylist, list.length); 789 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 790 return; 791 } 792 try { 793 if (force_shuffle) { 794 sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL); 795 } 796 long curid = sService.getAudioId(); 797 int curpos = sService.getQueuePosition(); 798 if (position != -1 && curpos == position && curid == list[position]) { 799 // The selected file is the file that's currently playing; 800 // figure out if we need to restart with a new playlist, 801 // or just launch the playback activity. 802 long[] playlist = sService.getQueue(); 803 if (Arrays.equals(list, playlist)) { 804 // we don't need to set a new list, but we should resume playback if needed 805 sService.play(); 806 return; // the 'finally' block will still run 807 } 808 } 809 if (position < 0) { 810 position = 0; 811 } 812 sService.open(list, force_shuffle ? -1 : position); 813 sService.play(); 814 } catch (RemoteException ex) { 815 } finally { 816 Intent intent = new Intent("com.android.music.PLAYBACK_VIEWER") 817 .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 818 context.startActivity(intent); 819 } 820 } 821 clearQueue()822 public static void clearQueue() { 823 try { 824 sService.removeTracks(0, Integer.MAX_VALUE); 825 } catch (RemoteException ex) { 826 } 827 } 828 829 // A really simple BitmapDrawable-like class, that doesn't do 830 // scaling, dithering or filtering. 831 private static class FastBitmapDrawable extends Drawable { 832 private Bitmap mBitmap; FastBitmapDrawable(Bitmap b)833 public FastBitmapDrawable(Bitmap b) { 834 mBitmap = b; 835 } 836 @Override draw(Canvas canvas)837 public void draw(Canvas canvas) { 838 canvas.drawBitmap(mBitmap, 0, 0, null); 839 } 840 @Override getOpacity()841 public int getOpacity() { 842 return PixelFormat.OPAQUE; 843 } 844 @Override setAlpha(int alpha)845 public void setAlpha(int alpha) {} 846 @Override setColorFilter(ColorFilter cf)847 public void setColorFilter(ColorFilter cf) {} 848 } 849 850 private static int sArtId = -2; 851 private static Bitmap mCachedBit = null; 852 private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options(); 853 private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options(); 854 private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart"); 855 private static final HashMap<Long, Drawable> sArtCache = new HashMap<Long, Drawable>(); 856 private static int sArtCacheId = -1; 857 858 static { 859 // for the cache, 860 // 565 is faster to decode and display 861 // and we don't want to dither here because the image will be scaled down later 862 sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565; 863 sBitmapOptionsCache.inDither = false; 864 865 sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; 866 sBitmapOptions.inDither = false; 867 } 868 initAlbumArtCache()869 public static void initAlbumArtCache() { 870 try { 871 int id = sService.getMediaMountedCount(); 872 if (id != sArtCacheId) { 873 clearAlbumArtCache(); 874 sArtCacheId = id; 875 } 876 } catch (RemoteException e) { 877 e.printStackTrace(); 878 } 879 } 880 clearAlbumArtCache()881 public static void clearAlbumArtCache() { 882 synchronized (sArtCache) { 883 sArtCache.clear(); 884 } 885 } 886 getCachedArtwork( Context context, long artIndex, BitmapDrawable defaultArtwork)887 public static Drawable getCachedArtwork( 888 Context context, long artIndex, BitmapDrawable defaultArtwork) { 889 Drawable d = null; 890 synchronized (sArtCache) { 891 d = sArtCache.get(artIndex); 892 } 893 if (d == null) { 894 d = defaultArtwork; 895 final Bitmap icon = defaultArtwork.getBitmap(); 896 int w = icon.getWidth(); 897 int h = icon.getHeight(); 898 Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h); 899 if (b != null) { 900 d = new FastBitmapDrawable(b); 901 synchronized (sArtCache) { 902 // the cache may have changed since we checked 903 Drawable value = sArtCache.get(artIndex); 904 if (value == null) { 905 sArtCache.put(artIndex, d); 906 } else { 907 d = value; 908 } 909 } 910 } 911 } 912 return d; 913 } 914 915 // Get album art for specified album. This method will not try to 916 // fall back to getting artwork directly from the file, nor will 917 // it attempt to repair the database. getArtworkQuick(Context context, long album_id, int w, int h)918 private static Bitmap getArtworkQuick(Context context, long album_id, int w, int h) { 919 // NOTE: There is in fact a 1 pixel border on the right side in the ImageView 920 // used to display this drawable. Take it into account now, so we don't have to 921 // scale later. 922 w -= 1; 923 ContentResolver res = context.getContentResolver(); 924 Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id); 925 if (uri != null) { 926 ParcelFileDescriptor fd = null; 927 try { 928 fd = res.openFileDescriptor(uri, "r"); 929 int sampleSize = 1; 930 931 // Compute the closest power-of-two scale factor 932 // and pass that to sBitmapOptionsCache.inSampleSize, which will 933 // result in faster decoding and better quality 934 sBitmapOptionsCache.inJustDecodeBounds = true; 935 BitmapFactory.decodeFileDescriptor( 936 fd.getFileDescriptor(), null, sBitmapOptionsCache); 937 int nextWidth = sBitmapOptionsCache.outWidth >> 1; 938 int nextHeight = sBitmapOptionsCache.outHeight >> 1; 939 while (nextWidth > w && nextHeight > h) { 940 sampleSize <<= 1; 941 nextWidth >>= 1; 942 nextHeight >>= 1; 943 } 944 945 sBitmapOptionsCache.inSampleSize = sampleSize; 946 sBitmapOptionsCache.inJustDecodeBounds = false; 947 Bitmap b = BitmapFactory.decodeFileDescriptor( 948 fd.getFileDescriptor(), null, sBitmapOptionsCache); 949 950 if (b != null) { 951 // finally rescale to exactly the size we need 952 if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) { 953 Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true); 954 // Bitmap.createScaledBitmap() can return the same bitmap 955 if (tmp != b) b.recycle(); 956 b = tmp; 957 } 958 } 959 960 return b; 961 } catch (FileNotFoundException e) { 962 } finally { 963 try { 964 if (fd != null) fd.close(); 965 } catch (IOException e) { 966 } 967 } 968 } 969 return null; 970 } 971 972 /** Get album art for specified album. You should not pass in the album id 973 * for the "unknown" album here (use -1 instead) 974 * This method always returns the default album art icon when no album art is found. 975 */ getArtwork(Context context, long song_id, long album_id)976 public static Bitmap getArtwork(Context context, long song_id, long album_id) { 977 return getArtwork(context, song_id, album_id, true); 978 } 979 980 /** Get album art for specified album. You should not pass in the album id 981 * for the "unknown" album here (use -1 instead) 982 */ getArtwork( Context context, long song_id, long album_id, boolean allowdefault)983 public static Bitmap getArtwork( 984 Context context, long song_id, long album_id, boolean allowdefault) { 985 if (album_id < 0) { 986 // This is something that is not in the database, so get the album art directly 987 // from the file. 988 if (song_id >= 0) { 989 Bitmap bm = getArtworkFromFile(context, song_id, -1); 990 if (bm != null) { 991 return bm; 992 } 993 } 994 if (allowdefault) { 995 return getDefaultArtwork(context); 996 } 997 return null; 998 } 999 1000 ContentResolver res = context.getContentResolver(); 1001 Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id); 1002 if (uri != null) { 1003 InputStream in = null; 1004 try { 1005 in = res.openInputStream(uri); 1006 return BitmapFactory.decodeStream(in, null, sBitmapOptions); 1007 } catch (FileNotFoundException ex) { 1008 // The album art thumbnail does not actually exist. Maybe the user deleted it, or 1009 // maybe it never existed to begin with. 1010 Bitmap bm = getArtworkFromFile(context, song_id, album_id); 1011 if (bm != null) { 1012 if (bm.getConfig() == null) { 1013 bm = bm.copy(Bitmap.Config.RGB_565, false); 1014 if (bm == null && allowdefault) { 1015 return getDefaultArtwork(context); 1016 } 1017 } 1018 } else if (allowdefault) { 1019 bm = getDefaultArtwork(context); 1020 } 1021 return bm; 1022 } finally { 1023 try { 1024 if (in != null) { 1025 in.close(); 1026 } 1027 } catch (IOException ex) { 1028 } 1029 } 1030 } 1031 1032 return null; 1033 } 1034 1035 // get album art for specified file 1036 private static final String sExternalMediaUri = 1037 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString(); getArtworkFromFile(Context context, long songid, long albumid)1038 private static Bitmap getArtworkFromFile(Context context, long songid, long albumid) { 1039 Bitmap bm = null; 1040 byte[] art = null; 1041 String path = null; 1042 1043 if (albumid < 0 && songid < 0) { 1044 throw new IllegalArgumentException("Must specify an album or a song id"); 1045 } 1046 1047 try { 1048 if (albumid < 0) { 1049 Uri uri = Uri.parse("content://media/external/audio/media/" + songid + "/albumart"); 1050 ParcelFileDescriptor pfd = 1051 context.getContentResolver().openFileDescriptor(uri, "r"); 1052 if (pfd != null) { 1053 FileDescriptor fd = pfd.getFileDescriptor(); 1054 bm = BitmapFactory.decodeFileDescriptor(fd); 1055 } 1056 } else { 1057 Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid); 1058 ParcelFileDescriptor pfd = 1059 context.getContentResolver().openFileDescriptor(uri, "r"); 1060 if (pfd != null) { 1061 FileDescriptor fd = pfd.getFileDescriptor(); 1062 bm = BitmapFactory.decodeFileDescriptor(fd); 1063 } 1064 } 1065 } catch (IllegalStateException ex) { 1066 } catch (FileNotFoundException ex) { 1067 } 1068 if (bm != null) { 1069 mCachedBit = bm; 1070 } 1071 return bm; 1072 } 1073 getDefaultArtwork(Context context)1074 private static Bitmap getDefaultArtwork(Context context) { 1075 BitmapFactory.Options opts = new BitmapFactory.Options(); 1076 opts.inPreferredConfig = Bitmap.Config.ARGB_8888; 1077 return BitmapFactory.decodeStream( 1078 context.getResources().openRawResource(R.drawable.albumart_mp_unknown), null, opts); 1079 } 1080 getIntPref(Context context, String name, int def)1081 static int getIntPref(Context context, String name, int def) { 1082 SharedPreferences prefs = 1083 context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); 1084 return prefs.getInt(name, def); 1085 } 1086 setIntPref(Context context, String name, int value)1087 static void setIntPref(Context context, String name, int value) { 1088 SharedPreferences prefs = 1089 context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); 1090 Editor ed = prefs.edit(); 1091 ed.putInt(name, value); 1092 SharedPreferencesCompat.apply(ed); 1093 } 1094 setRingtone(Context context, long id)1095 static void setRingtone(Context context, long id) { 1096 ContentResolver resolver = context.getContentResolver(); 1097 // Set the flag in the database to mark this as a ringtone 1098 Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); 1099 try { 1100 ContentValues values = new ContentValues(2); 1101 values.put(MediaStore.Audio.Media.IS_RINGTONE, "1"); 1102 values.put(MediaStore.Audio.Media.IS_ALARM, "1"); 1103 resolver.update(ringUri, values, null, null); 1104 } catch (UnsupportedOperationException ex) { 1105 // most likely the card just got unmounted 1106 Log.e(TAG, "couldn't set ringtone flag for id " + id); 1107 return; 1108 } 1109 1110 String[] cols = new String[] {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA, 1111 MediaStore.Audio.Media.TITLE}; 1112 1113 String where = MediaStore.Audio.Media._ID + "=" + id; 1114 Cursor cursor = query( 1115 context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, where, null, null); 1116 try { 1117 if (cursor != null && cursor.getCount() == 1) { 1118 // Set the system setting to make this the current ringtone 1119 cursor.moveToFirst(); 1120 Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString()); 1121 String message = context.getString(R.string.ringtone_set, cursor.getString(2)); 1122 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 1123 } 1124 } finally { 1125 if (cursor != null) { 1126 cursor.close(); 1127 } 1128 } 1129 } 1130 1131 static int sActiveTabIndex = -1; 1132 updateButtonBar(Activity a, int highlight)1133 static boolean updateButtonBar(Activity a, int highlight) { 1134 final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar); 1135 boolean withtabs = false; 1136 Intent intent = a.getIntent(); 1137 if (intent != null) { 1138 withtabs = intent.getBooleanExtra("withtabs", false); 1139 } 1140 1141 if (highlight == 0 || !withtabs) { 1142 ll.setVisibility(View.GONE); 1143 return withtabs; 1144 } else if (withtabs) { 1145 ll.setVisibility(View.VISIBLE); 1146 } 1147 for (int i = ll.getChildCount() - 1; i >= 0; i--) { 1148 View v = ll.getChildAt(i); 1149 boolean isActive = (v.getId() == highlight); 1150 if (isActive) { 1151 ll.setCurrentTab(i); 1152 sActiveTabIndex = i; 1153 } 1154 v.setTag(i); 1155 v.setOnFocusChangeListener(new View.OnFocusChangeListener() { 1156 1157 public void onFocusChange(View v, boolean hasFocus) { 1158 if (hasFocus) { 1159 for (int i = 0; i < ll.getTabCount(); i++) { 1160 if (ll.getChildTabViewAt(i) == v) { 1161 ll.setCurrentTab(i); 1162 processTabClick((Activity) ll.getContext(), v, 1163 ll.getChildAt(sActiveTabIndex).getId()); 1164 break; 1165 } 1166 } 1167 } 1168 } 1169 }); 1170 1171 v.setOnClickListener(new View.OnClickListener() { 1172 1173 public void onClick(View v) { 1174 processTabClick( 1175 (Activity) ll.getContext(), v, ll.getChildAt(sActiveTabIndex).getId()); 1176 } 1177 }); 1178 } 1179 return withtabs; 1180 } 1181 processTabClick(Activity a, View v, int current)1182 static void processTabClick(Activity a, View v, int current) { 1183 int id = v.getId(); 1184 if (id == current) { 1185 return; 1186 } 1187 1188 final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar); 1189 1190 activateTab(a, id); 1191 if (id != R.id.nowplayingtab) { 1192 ll.setCurrentTab((Integer) v.getTag()); 1193 setIntPref(a, "activetab", id); 1194 } 1195 } 1196 activateTab(Activity a, int id)1197 static void activateTab(Activity a, int id) { 1198 Intent intent = new Intent(Intent.ACTION_PICK); 1199 switch (id) { 1200 case R.id.artisttab: 1201 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/artistalbum"); 1202 break; 1203 case R.id.albumtab: 1204 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album"); 1205 break; 1206 case R.id.songtab: 1207 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 1208 break; 1209 case R.id.playlisttab: 1210 intent.setDataAndType(Uri.EMPTY, MediaStore.Audio.Playlists.CONTENT_TYPE); 1211 break; 1212 case R.id.nowplayingtab: 1213 intent = new Intent(a, MediaPlaybackActivity.class); 1214 a.startActivity(intent); 1215 // fall through and return 1216 default: 1217 return; 1218 } 1219 intent.putExtra("withtabs", true); 1220 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 1221 a.startActivity(intent); 1222 a.finish(); 1223 a.overridePendingTransition(0, 0); 1224 } 1225 updateNowPlaying(Activity a)1226 static void updateNowPlaying(Activity a) { 1227 View nowPlayingView = a.findViewById(R.id.nowplaying); 1228 if (nowPlayingView == null) { 1229 return; 1230 } 1231 try { 1232 boolean withtabs = false; 1233 Intent intent = a.getIntent(); 1234 if (intent != null) { 1235 withtabs = intent.getBooleanExtra("withtabs", false); 1236 } 1237 if (true && MusicUtils.sService != null && MusicUtils.sService.getAudioId() != -1) { 1238 TextView title = (TextView) nowPlayingView.findViewById(R.id.title); 1239 TextView artist = (TextView) nowPlayingView.findViewById(R.id.artist); 1240 title.setText(MusicUtils.sService.getTrackName()); 1241 String artistName = MusicUtils.sService.getArtistName(); 1242 if (MediaStore.UNKNOWN_STRING.equals(artistName)) { 1243 artistName = a.getString(R.string.unknown_artist_name); 1244 } 1245 artist.setText(artistName); 1246 // mNowPlayingView.setOnFocusChangeListener(mFocuser); 1247 // mNowPlayingView.setOnClickListener(this); 1248 nowPlayingView.setVisibility(View.VISIBLE); 1249 nowPlayingView.setOnClickListener(new View.OnClickListener() { 1250 1251 public void onClick(View v) { 1252 Context c = v.getContext(); 1253 c.startActivity(new Intent(c, MediaPlaybackActivity.class)); 1254 } 1255 }); 1256 return; 1257 } 1258 } catch (RemoteException ex) { 1259 } 1260 nowPlayingView.setVisibility(View.GONE); 1261 } 1262 setBackground(View v, Bitmap bm)1263 static void setBackground(View v, Bitmap bm) { 1264 if (bm == null) { 1265 v.setBackgroundResource(0); 1266 return; 1267 } 1268 1269 int vwidth = v.getWidth(); 1270 int vheight = v.getHeight(); 1271 int bwidth = bm.getWidth(); 1272 int bheight = bm.getHeight(); 1273 float scalex = (float) vwidth / bwidth; 1274 float scaley = (float) vheight / bheight; 1275 float scale = Math.max(scalex, scaley) * 1.3f; 1276 1277 Bitmap.Config config = Bitmap.Config.ARGB_8888; 1278 Bitmap bg = Bitmap.createBitmap(vwidth, vheight, config); 1279 Canvas c = new Canvas(bg); 1280 Paint paint = new Paint(); 1281 paint.setAntiAlias(true); 1282 paint.setFilterBitmap(true); 1283 ColorMatrix greymatrix = new ColorMatrix(); 1284 greymatrix.setSaturation(0); 1285 ColorMatrix darkmatrix = new ColorMatrix(); 1286 darkmatrix.setScale(.3f, .3f, .3f, 1.0f); 1287 greymatrix.postConcat(darkmatrix); 1288 ColorFilter filter = new ColorMatrixColorFilter(greymatrix); 1289 paint.setColorFilter(filter); 1290 Matrix matrix = new Matrix(); 1291 matrix.setTranslate(-bwidth / 2, -bheight / 2); // move bitmap center to origin 1292 matrix.postRotate(10); 1293 matrix.postScale(scale, scale); 1294 matrix.postTranslate(vwidth / 2, vheight / 2); // Move bitmap center to view center 1295 c.drawBitmap(bm, matrix, paint); 1296 v.setBackgroundDrawable(new BitmapDrawable(bg)); 1297 } 1298 getCardId(Context context)1299 static int getCardId(Context context) { 1300 ContentResolver res = context.getContentResolver(); 1301 Cursor c = res.query(Uri.parse("content://media/external/fs_id"), null, null, null, null); 1302 int id = -1; 1303 if (c != null) { 1304 c.moveToFirst(); 1305 id = c.getInt(0); 1306 c.close(); 1307 } 1308 return id; 1309 } 1310 1311 static class LogEntry { 1312 Object item; 1313 long time; 1314 LogEntry(Object o)1315 LogEntry(Object o) { 1316 item = o; 1317 time = System.currentTimeMillis(); 1318 } 1319 dump(PrintWriter out)1320 void dump(PrintWriter out) { 1321 sTime.set(time); 1322 out.print(sTime.toString() + " : "); 1323 if (item instanceof Exception) { 1324 ((Exception) item).printStackTrace(out); 1325 } else { 1326 out.println(item); 1327 } 1328 } 1329 } 1330 1331 private static LogEntry[] sMusicLog = new LogEntry[100]; 1332 private static int sLogPtr = 0; 1333 private static Time sTime = new Time(); 1334 debugLog(Object o)1335 static void debugLog(Object o) { 1336 sMusicLog[sLogPtr] = new LogEntry(o); 1337 sLogPtr++; 1338 if (sLogPtr >= sMusicLog.length) { 1339 sLogPtr = 0; 1340 } 1341 } 1342 debugDump(PrintWriter out)1343 static void debugDump(PrintWriter out) { 1344 for (int i = 0; i < sMusicLog.length; i++) { 1345 int idx = (sLogPtr + i); 1346 if (idx >= sMusicLog.length) { 1347 idx -= sMusicLog.length; 1348 } 1349 LogEntry entry = sMusicLog[idx]; 1350 if (entry != null) { 1351 entry.dump(out); 1352 } 1353 } 1354 } 1355 } 1356