Project: platform_packages_apps_music
/*
 * Copyright (C) 2008 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
package com.android.music; 
 
import android.app.Activity; 
import android.content.ComponentName; 
import android.content.ContentResolver; 
import android.content.ContentUris; 
import android.content.ContentValues; 
import android.content.Context; 
import android.content.ContextWrapper; 
import android.content.Intent; 
import android.content.ServiceConnection; 
import android.content.SharedPreferences; 
import android.content.SharedPreferences.Editor; 
import android.content.res.Resources; 
import android.database.Cursor; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.ColorFilter; 
import android.graphics.ColorMatrix; 
import android.graphics.ColorMatrixColorFilter; 
import android.graphics.Matrix; 
import android.graphics.Paint; 
import android.graphics.PixelFormat; 
import android.graphics.drawable.BitmapDrawable; 
import android.graphics.drawable.Drawable; 
import android.net.Uri; 
import android.os.Environment; 
import android.os.ParcelFileDescriptor; 
import android.os.RemoteException; 
import android.provider.MediaStore; 
import android.provider.Settings; 
import android.text.TextUtils; 
import android.text.format.Time; 
import android.util.Log; 
import android.view.Menu; 
import android.view.MenuItem; 
import android.view.SubMenu; 
import android.view.View; 
import android.view.Window; 
import android.widget.TabWidget; 
import android.widget.TextView; 
import android.widget.Toast; 
 
import java.io.File; 
import java.io.FileDescriptor; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.PrintWriter; 
import java.util.Arrays; 
import java.util.Formatter; 
import java.util.HashMap; 
import java.util.Locale; 
 
public class MusicUtils { 
 
    private static final String TAG = "MusicUtils"
 
    public interface Defs { 
        public final static int OPEN_URL = 0
        public final static int ADD_TO_PLAYLIST = 1
        public final static int USE_AS_RINGTONE = 2
        public final static int PLAYLIST_SELECTED = 3
        public final static int NEW_PLAYLIST = 4
        public final static int PLAY_SELECTION = 5
        public final static int GOTO_START = 6
        public final static int GOTO_PLAYBACK = 7
        public final static int PARTY_SHUFFLE = 8
        public final static int SHUFFLE_ALL = 9
        public final static int DELETE_ITEM = 10
        public final static int SCAN_DONE = 11
        public final static int QUEUE = 12
        public final static int EFFECTS_PANEL = 13
        public final static int CHILD_MENU_BASE = 14// this should be the last item 
    } 
 
    public static String makeAlbumsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) { 
        // There are two formats for the albums/songs information: 
        // "N Song(s)"  - used for unknown artist/album 
        // "N Album(s)" - used for known albums 
         
        StringBuilder songs_albums = new StringBuilder(); 
 
        Resources r = context.getResources(); 
        if (isUnknown) { 
            if (numsongs == 1) { 
                songs_albums.append(context.getString(R.string.onesong)); 
            } else { 
                String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString(); 
                sFormatBuilder.setLength(0); 
                sFormatter.format(f, Integer.valueOf(numsongs)); 
                songs_albums.append(sFormatBuilder); 
            } 
        } else { 
            String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString(); 
            sFormatBuilder.setLength(0); 
            sFormatter.format(f, Integer.valueOf(numalbums)); 
            songs_albums.append(sFormatBuilder); 
            songs_albums.append(context.getString(R.string.albumsongseparator)); 
        } 
        return songs_albums.toString(); 
    } 
 
    /**
     * This is now only used for the query screen 
     */
 
    public static String makeAlbumsSongsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) { 
        // There are several formats for the albums/songs information: 
        // "1 Song"   - used if there is only 1 song 
        // "N Songs" - used for the "unknown artist" item 
        // "1 Album"/"N Songs"  
        // "N Album"/"M Songs" 
        // Depending on locale, these may need to be further subdivided 
         
        StringBuilder songs_albums = new StringBuilder(); 
 
        if (numsongs == 1) { 
            songs_albums.append(context.getString(R.string.onesong)); 
        } else { 
            Resources r = context.getResources(); 
            if (! isUnknown) { 
                String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString(); 
                sFormatBuilder.setLength(0); 
                sFormatter.format(f, Integer.valueOf(numalbums)); 
                songs_albums.append(sFormatBuilder); 
                songs_albums.append(context.getString(R.string.albumsongseparator)); 
            } 
            String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString(); 
            sFormatBuilder.setLength(0); 
            sFormatter.format(f, Integer.valueOf(numsongs)); 
            songs_albums.append(sFormatBuilder); 
        } 
        return songs_albums.toString(); 
    } 
     
    public static IMediaPlaybackService sService = null
    private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>(); 
 
    public static class ServiceToken { 
        ContextWrapper mWrappedContext; 
        ServiceToken(ContextWrapper context) { 
            mWrappedContext = context; 
        } 
    } 
 
    public static ServiceToken bindToService(Activity context) { 
        return bindToService(context, null); 
    } 
 
    public static ServiceToken bindToService(Activity context, ServiceConnection callback) { 
        Activity realActivity = context.getParent(); 
        if (realActivity == null) { 
            realActivity = context; 
        } 
        ContextWrapper cw = new ContextWrapper(realActivity); 
        cw.startService(new Intent(cw, MediaPlaybackService.class)); 
        ServiceBinder sb = new ServiceBinder(callback); 
        if (cw.bindService((new Intent()).setClass(cw, MediaPlaybackService.class), sb, 0)) { 
            sConnectionMap.put(cw, sb); 
            return new ServiceToken(cw); 
        } 
        Log.e("Music""Failed to bind to service"); 
        return null
    } 
 
    public static void unbindFromService(ServiceToken token) { 
        if (token == null) { 
            Log.e("MusicUtils""Trying to unbind with null token"); 
            return
        } 
        ContextWrapper cw = token.mWrappedContext; 
        ServiceBinder sb = sConnectionMap.remove(cw); 
        if (sb == null) { 
            Log.e("MusicUtils""Trying to unbind for unknown Context"); 
            return
        } 
        cw.unbindService(sb); 
        if (sConnectionMap.isEmpty()) { 
            // presumably there is nobody interested in the service at this point, 
            // so don't hang on to the ServiceConnection 
            sService = null
        } 
    } 
 
    private static class ServiceBinder implements ServiceConnection { 
        ServiceConnection mCallback; 
        ServiceBinder(ServiceConnection callback) { 
            mCallback = callback; 
        } 
         
        public void onServiceConnected(ComponentName className, android.os.IBinder service) { 
            sService = IMediaPlaybackService.Stub.asInterface(service); 
            initAlbumArtCache(); 
            if (mCallback != null) { 
                mCallback.onServiceConnected(className, service); 
            } 
        } 
         
        public void onServiceDisconnected(ComponentName className) { 
            if (mCallback != null) { 
                mCallback.onServiceDisconnected(className); 
            } 
            sService = null
        } 
    } 
     
    public static long getCurrentAlbumId() { 
        if (sService != null) { 
            try { 
                return sService.getAlbumId(); 
            } catch (RemoteException ex) { 
            } 
        } 
        return -1
    } 
 
    public static long getCurrentArtistId() { 
        if (MusicUtils.sService != null) { 
            try { 
                return sService.getArtistId(); 
            } catch (RemoteException ex) { 
            } 
        } 
        return -1
    } 
 
    public static long getCurrentAudioId() { 
        if (MusicUtils.sService != null) { 
            try { 
                return sService.getAudioId(); 
            } catch (RemoteException ex) { 
            } 
        } 
        return -1
    } 
     
    public static int getCurrentShuffleMode() { 
        int mode = MediaPlaybackService.SHUFFLE_NONE; 
        if (sService != null) { 
            try { 
                mode = sService.getShuffleMode(); 
            } catch (RemoteException ex) { 
            } 
        } 
        return mode; 
    } 
     
    public static void togglePartyShuffle() { 
        if (sService != null) { 
            int shuffle = getCurrentShuffleMode(); 
            try { 
                if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) { 
                    sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE); 
                } else { 
                    sService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO); 
                } 
            } catch (RemoteException ex) { 
            } 
        } 
    } 
     
    public static void setPartyShuffleMenuIcon(Menu menu) { 
        MenuItem item = menu.findItem(Defs.PARTY_SHUFFLE); 
        if (item != null) { 
            int shuffle = MusicUtils.getCurrentShuffleMode(); 
            if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) { 
                item.setIcon(R.drawable.ic_menu_party_shuffle); 
                item.setTitle(R.string.party_shuffle_off); 
            } else { 
                item.setIcon(R.drawable.ic_menu_party_shuffle); 
                item.setTitle(R.string.party_shuffle); 
            } 
        } 
    } 
     
    /*
     * Returns true if a file is currently opened for playback (regardless 
     * of whether it's playing or paused). 
     */
 
    public static boolean isMusicLoaded() { 
        if (MusicUtils.sService != null) { 
            try { 
                return sService.getPath() != null
            } catch (RemoteException ex) { 
            } 
        } 
        return false
    } 
 
    private final static long [] sEmptyList = new long[0]; 
 
    public static long [] getSongListForCursor(Cursor cursor) { 
        if (cursor == null) { 
            return sEmptyList; 
        } 
        int len = cursor.getCount(); 
        long [] list = new long[len]; 
        cursor.moveToFirst(); 
        int colidx = -1
        try { 
            colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID); 
        } catch (IllegalArgumentException ex) { 
            colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); 
        } 
        for (int i = 0; i < len; i++) { 
            list[i] = cursor.getLong(colidx); 
            cursor.moveToNext(); 
        } 
        return list; 
    } 
 
    public static long [] getSongListForArtist(Context context, long id) { 
        final String[] ccols = new String[] { MediaStore.Audio.Media._ID }; 
        String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND " +  
        MediaStore.Audio.Media.IS_MUSIC + "=1"
        Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                ccols, where, null
                MediaStore.Audio.Media.ALBUM_KEY + ","  + MediaStore.Audio.Media.TRACK); 
         
        if (cursor != null) { 
            long [] list = getSongListForCursor(cursor); 
            cursor.close(); 
            return list; 
        } 
        return sEmptyList; 
    } 
 
    public static long [] getSongListForAlbum(Context context, long id) { 
        final String[] ccols = new String[] { MediaStore.Audio.Media._ID }; 
        String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND " +  
                MediaStore.Audio.Media.IS_MUSIC + "=1"
        Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                ccols, where, null, MediaStore.Audio.Media.TRACK); 
 
        if (cursor != null) { 
            long [] list = getSongListForCursor(cursor); 
            cursor.close(); 
            return list; 
        } 
        return sEmptyList; 
    } 
 
    public static long [] getSongListForPlaylist(Context context, long plid) { 
        final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID }; 
        Cursor cursor = query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid), 
                ccols, nullnull, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); 
         
        if (cursor != null) { 
            long [] list = getSongListForCursor(cursor); 
            cursor.close(); 
            return list; 
        } 
        return sEmptyList; 
    } 
     
    public static void playPlaylist(Context context, long plid) { 
        long [] list = getSongListForPlaylist(context, plid); 
        if (list != null) { 
            playAll(context, list, -1false); 
        } 
    } 
 
    public static long [] getAllSongs(Context context) { 
        Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1"
                nullnull); 
        try { 
            if (c == null || c.getCount() == 0) { 
                return null
            } 
            int len = c.getCount(); 
            long [] list = new long[len]; 
            for (int i = 0; i < len; i++) { 
                c.moveToNext(); 
                list[i] = c.getLong(0); 
            } 
 
            return list; 
        } finally { 
            if (c != null) { 
                c.close(); 
            } 
        } 
    } 
 
    /**
     * Fills out the given submenu with items for "new playlist" and 
     * any existing playlists. When the user selects an item, the 
     * application will receive PLAYLIST_SELECTED with the Uri of 
     * the selected playlist, NEW_PLAYLIST if a new playlist 
     * should be created, and QUEUE if the "current playlist" was 
     * selected. 
     * @param context The context to use for creating the menu items 
     * @param sub The submenu to add the items to. 
     */
 
    public static void makePlaylistMenu(Context context, SubMenu sub) { 
        String[] cols = new String[] { 
                MediaStore.Audio.Playlists._ID, 
                MediaStore.Audio.Playlists.NAME 
        }; 
        ContentResolver resolver = context.getContentResolver(); 
        if (resolver == null) { 
            System.out.println("resolver = null"); 
        } else { 
            String whereclause = MediaStore.Audio.Playlists.NAME + " != ''"
            Cursor cur = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, 
                cols, whereclause, null
                MediaStore.Audio.Playlists.NAME); 
            sub.clear(); 
            sub.add(1, Defs.QUEUE, 0, R.string.queue); 
            sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist); 
            if (cur != null && cur.getCount() > 0) { 
                //sub.addSeparator(1, 0); 
                cur.moveToFirst(); 
                while (! cur.isAfterLast()) { 
                    Intent intent = new Intent(); 
                    intent.putExtra("playlist", cur.getLong(0)); 
//                    if (cur.getInt(0) == mLastPlaylistSelected) { 
//                        sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED, cur.getString(1)).setIntent(intent); 
//                    } else { 
                        sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1)).setIntent(intent); 
//                    } 
                    cur.moveToNext(); 
                } 
            } 
            if (cur != null) { 
                cur.close(); 
            } 
        } 
    } 
 
    public static void clearPlaylist(Context context, int plid) { 
         
        Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", plid); 
        context.getContentResolver().delete(uri, nullnull); 
        return
    } 
     
    public static void deleteTracks(Context context, long [] list) { 
         
        String [] cols = new String [] { MediaStore.Audio.Media._ID,  
                MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID }; 
        StringBuilder where = new StringBuilder(); 
        where.append(MediaStore.Audio.Media._ID + " IN ("); 
        for (int i = 0; i < list.length; i++) { 
            where.append(list[i]); 
            if (i < list.length - 1) { 
                where.append(","); 
            } 
        } 
        where.append(")"); 
        Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, 
                where.toString(), nullnull); 
 
        if (c != null) { 
 
            // step 1: remove selected tracks from the current playlist, as well 
            // as from the album art cache 
            try { 
                c.moveToFirst(); 
                while (! c.isAfterLast()) { 
                    // remove from current playlist 
                    long id = c.getLong(0); 
                    sService.removeTrack(id); 
                    // remove from album art cache 
                    long artIndex = c.getLong(2); 
                    synchronized(sArtCache) { 
                        sArtCache.remove(artIndex); 
                    } 
                    c.moveToNext(); 
                } 
            } catch (RemoteException ex) { 
            } 
 
            // step 2: remove selected tracks from the database 
            context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where.toString(), null); 
 
            // step 3: remove files from card 
            c.moveToFirst(); 
            while (! c.isAfterLast()) { 
                String name = c.getString(1); 
                File f = new File(name); 
                try {  // File.delete can throw a security exception 
                    if (!f.delete()) { 
                        // I'm not sure if we'd ever get here (deletion would 
                        // have to fail, but no exception thrown) 
                        Log.e("MusicUtils""Failed to delete file " + name); 
                    } 
                    c.moveToNext(); 
                } catch (SecurityException ex) { 
                    c.moveToNext(); 
                } 
            } 
            c.close(); 
        } 
 
        String message = context.getResources().getQuantityString( 
                R.plurals.NNNtracksdeleted, list.length, Integer.valueOf(list.length)); 
         
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 
        // We deleted a number of tracks, which could affect any number of things 
        // in the media content domain, so update everything. 
        context.getContentResolver().notifyChange(Uri.parse("content://media"), null); 
    } 
     
    public static void addToCurrentPlaylist(Context context, long [] list) { 
        if (sService == null) { 
            return
        } 
        try { 
            sService.enqueue(list, MediaPlaybackService.LAST); 
            String message = context.getResources().getQuantityString( 
                    R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length)); 
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 
        } catch (RemoteException ex) { 
        } 
    } 
 
    private static ContentValues[] sContentValuesCache = null
 
    /**
     * @param ids The source array containing all the ids to be added to the playlist 
     * @param offset Where in the 'ids' array we start reading 
     * @param len How many items to copy during this pass 
     * @param base The play order offset to use for this pass 
     */
 
    private static void makeInsertItems(long[] ids, int offset, int len, int base) { 
        // adjust 'len' if would extend beyond the end of the source array 
        if (offset + len > ids.length) { 
            len = ids.length - offset; 
        } 
        // allocate the ContentValues array, or reallocate if it is the wrong size 
        if (sContentValuesCache == null || sContentValuesCache.length != len) { 
            sContentValuesCache = new ContentValues[len]; 
        } 
        // fill in the ContentValues array with the right values for this pass 
        for (int i = 0; i < len; i++) { 
            if (sContentValuesCache[i] == null) { 
                sContentValuesCache[i] = new ContentValues(); 
            } 
 
            sContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i); 
            sContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]); 
        } 
    } 
     
    public static void addToPlaylist(Context context, long [] ids, long playlistid) { 
        if (ids == null) { 
            // this shouldn't happen (the menuitems shouldn't be visible 
            // unless the selected item represents something playable 
            Log.e("MusicBase""ListSelection null"); 
        } else { 
            int size = ids.length; 
            ContentResolver resolver = context.getContentResolver(); 
            // need to determine the number of items currently in the playlist, 
            // so the play_order field can be maintained. 
            String[] cols = new String[] { 
                    "count(*)" 
            }; 
            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid); 
            Cursor cur = resolver.query(uri, cols, nullnullnull); 
            cur.moveToFirst(); 
            int base = cur.getInt(0); 
            cur.close(); 
            int numinserted = 0
            for (int i = 0; i < size; i += 1000) { 
                makeInsertItems(ids, i, 1000, base); 
                numinserted += resolver.bulkInsert(uri, sContentValuesCache); 
            } 
            String message = context.getResources().getQuantityString( 
                    R.plurals.NNNtrackstoplaylist, numinserted, numinserted); 
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 
            //mLastPlaylistSelected = playlistid; 
        } 
    } 
 
    public static Cursor query(Context context, Uri uri, String[] projection, 
            String selection, String[] selectionArgs, String sortOrder, int limit) { 
        try { 
            ContentResolver resolver = context.getContentResolver(); 
            if (resolver == null) { 
                return null
            } 
            if (limit > 0) { 
                uri = uri.buildUpon().appendQueryParameter("limit""" + limit).build(); 
            } 
            return resolver.query(uri, projection, selection, selectionArgs, sortOrder); 
         } catch (UnsupportedOperationException ex) { 
            return null
        } 
         
    } 
    public static Cursor query(Context context, Uri uri, String[] projection, 
            String selection, String[] selectionArgs, String sortOrder) { 
        return query(context, uri, projection, selection, selectionArgs, sortOrder, 0); 
    } 
     
    public static boolean isMediaScannerScanning(Context context) { 
        boolean result = false
        Cursor cursor = query(context, MediaStore.getMediaScannerUri(),  
                new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, nullnullnull); 
        if (cursor != null) { 
            if (cursor.getCount() == 1) { 
                cursor.moveToFirst(); 
                result = "external".equals(cursor.getString(0)); 
            } 
            cursor.close();  
        }  
 
        return result; 
    } 
     
    public static void setSpinnerState(Activity a) { 
        if (isMediaScannerScanning(a)) { 
            // start the progress spinner 
            a.getWindow().setFeatureInt( 
                    Window.FEATURE_INDETERMINATE_PROGRESS, 
                    Window.PROGRESS_INDETERMINATE_ON); 
 
            a.getWindow().setFeatureInt( 
                    Window.FEATURE_INDETERMINATE_PROGRESS, 
                    Window.PROGRESS_VISIBILITY_ON); 
        } else { 
            // stop the progress spinner 
            a.getWindow().setFeatureInt( 
                    Window.FEATURE_INDETERMINATE_PROGRESS, 
                    Window.PROGRESS_VISIBILITY_OFF); 
        } 
    } 
     
    private static String mLastSdStatus; 
 
    public static void displayDatabaseError(Activity a) { 
        if (a.isFinishing()) { 
            // When switching tabs really fast, we can end up with a null 
            // cursor (not sure why), which will bring us here. 
            // Don't bother showing an error message in that case. 
            return
        } 
 
        String status = Environment.getExternalStorageState(); 
        int title, message; 
 
        if (android.os.Environment.isExternalStorageRemovable()) { 
            title = R.string.sdcard_error_title; 
            message = R.string.sdcard_error_message; 
        } else { 
            title = R.string.sdcard_error_title_nosdcard; 
            message = R.string.sdcard_error_message_nosdcard; 
        } 
         
        if (status.equals(Environment.MEDIA_SHARED) || 
                status.equals(Environment.MEDIA_UNMOUNTED)) { 
            if (android.os.Environment.isExternalStorageRemovable()) { 
                title = R.string.sdcard_busy_title; 
                message = R.string.sdcard_busy_message; 
            } else { 
                title = R.string.sdcard_busy_title_nosdcard; 
                message = R.string.sdcard_busy_message_nosdcard; 
            } 
        } else if (status.equals(Environment.MEDIA_REMOVED)) { 
            if (android.os.Environment.isExternalStorageRemovable()) { 
                title = R.string.sdcard_missing_title; 
                message = R.string.sdcard_missing_message; 
            } else { 
                title = R.string.sdcard_missing_title_nosdcard; 
                message = R.string.sdcard_missing_message_nosdcard; 
            } 
        } else if (status.equals(Environment.MEDIA_MOUNTED)){ 
            // The card is mounted, but we didn't get a valid cursor. 
            // This probably means the mediascanner hasn't started scanning the 
            // card yet (there is a small window of time during boot where this 
            // will happen). 
            a.setTitle(""); 
            Intent intent = new Intent(); 
            intent.setClass(a, ScanningProgress.class); 
            a.startActivityForResult(intent, Defs.SCAN_DONE); 
        } else if (!TextUtils.equals(mLastSdStatus, status)) { 
            mLastSdStatus = status; 
            Log.d(TAG, "sd card: " + status); 
        } 
 
        a.setTitle(title); 
        View v = a.findViewById(R.id.sd_message); 
        if (v != null) { 
            v.setVisibility(View.VISIBLE); 
        } 
        v = a.findViewById(R.id.sd_icon); 
        if (v != null) { 
            v.setVisibility(View.VISIBLE); 
        } 
        v = a.findViewById(android.R.id.list); 
        if (v != null) { 
            v.setVisibility(View.GONE); 
        } 
        v = a.findViewById(R.id.buttonbar); 
        if (v != null) { 
            v.setVisibility(View.GONE); 
        } 
        TextView tv = (TextView) a.findViewById(R.id.sd_message); 
        tv.setText(message); 
    } 
     
    public static void hideDatabaseError(Activity a) { 
        View v = a.findViewById(R.id.sd_message); 
        if (v != null) { 
            v.setVisibility(View.GONE); 
        } 
        v = a.findViewById(R.id.sd_icon); 
        if (v != null) { 
            v.setVisibility(View.GONE); 
        } 
        v = a.findViewById(android.R.id.list); 
        if (v != null) { 
            v.setVisibility(View.VISIBLE); 
        } 
    } 
 
    static protected Uri getContentURIForPath(String path) { 
        return Uri.fromFile(new File(path)); 
    } 
 
     
    /*  Try to use String.format() as little as possible, because it creates a
     *  new Formatter every time you call it, which is very inefficient. 
     *  Reusing an existing Formatter more than tripled the speed of 
     *  makeTimeString(). 
     *  This Formatter/StringBuilder are also used by makeAlbumSongsLabel() 
     */
 
    private static StringBuilder sFormatBuilder = new StringBuilder(); 
    private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault()); 
    private static final Object[] sTimeArgs = new Object[5]; 
 
    public static String makeTimeString(Context context, long secs) { 
        String durationformat = context.getString( 
                secs < 3600 ? R.string.durationformatshort : R.string.durationformatlong); 
         
        /* Provide multiple arguments so the format can be changed easily
         * by modifying the xml. 
         */
 
        sFormatBuilder.setLength(0); 
 
        final Object[] timeArgs = sTimeArgs; 
        timeArgs[0] = secs / 3600
        timeArgs[1] = secs / 60
        timeArgs[2] = (secs / 60) % 60
        timeArgs[3] = secs; 
        timeArgs[4] = secs % 60
 
        return sFormatter.format(durationformat, timeArgs).toString(); 
    } 
     
    public static void shuffleAll(Context context, Cursor cursor) { 
        playAll(context, cursor, 0true); 
    } 
 
    public static void playAll(Context context, Cursor cursor) { 
        playAll(context, cursor, 0false); 
    } 
     
    public static void playAll(Context context, Cursor cursor, int position) { 
        playAll(context, cursor, position, false); 
    } 
     
    public static void playAll(Context context, long [] list, int position) { 
        playAll(context, list, position, false); 
    } 
     
    private static void playAll(Context context, Cursor cursor, int position, boolean force_shuffle) { 
     
        long [] list = getSongListForCursor(cursor); 
        playAll(context, list, position, force_shuffle); 
    } 
     
    private static void playAll(Context context, long [] list, int position, boolean force_shuffle) { 
        if (list.length == 0 || sService == null) { 
            Log.d("MusicUtils""attempt to play empty song list"); 
            // Don't try to play empty playlists. Nothing good will come of it. 
            String message = context.getString(R.string.emptyplaylist, list.length); 
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 
            return
        } 
        try { 
            if (force_shuffle) { 
                sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL); 
            } 
            long curid = sService.getAudioId(); 
            int curpos = sService.getQueuePosition(); 
            if (position != -1 && curpos == position && curid == list[position]) { 
                // The selected file is the file that's currently playing; 
                // figure out if we need to restart with a new playlist, 
                // or just launch the playback activity. 
                long [] playlist = sService.getQueue(); 
                if (Arrays.equals(list, playlist)) { 
                    // we don't need to set a new list, but we should resume playback if needed 
                    sService.play(); 
                    return// the 'finally' block will still run 
                } 
            } 
            if (position < 0) { 
                position = 0
            } 
            sService.open(list, force_shuffle ? -1 : position); 
            sService.play(); 
        } catch (RemoteException ex) { 
        } finally { 
            Intent intent = new Intent("com.android.music.PLAYBACK_VIEWER"
                .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
            context.startActivity(intent); 
        } 
    } 
     
    public static void clearQueue() { 
        try { 
            sService.removeTracks(0, Integer.MAX_VALUE); 
        } catch (RemoteException ex) { 
        } 
    } 
     
    // A really simple BitmapDrawable-like class, that doesn't do 
    // scaling, dithering or filtering. 
    private static class FastBitmapDrawable extends Drawable { 
        private Bitmap mBitmap; 
        public FastBitmapDrawable(Bitmap b) { 
            mBitmap = b; 
        } 
        @Override 
        public void draw(Canvas canvas) { 
            canvas.drawBitmap(mBitmap, 00null); 
        } 
        @Override 
        public int getOpacity() { 
            return PixelFormat.OPAQUE; 
        } 
        @Override 
        public void setAlpha(int alpha) { 
        } 
        @Override 
        public void setColorFilter(ColorFilter cf) { 
        } 
    } 
     
    private static int sArtId = -2
    private static Bitmap mCachedBit = null
    private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options(); 
    private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options(); 
    private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart"); 
    private static final HashMap<Long, Drawable> sArtCache = new HashMap<Long, Drawable>(); 
    private static int sArtCacheId = -1
     
    static { 
        // for the cache,  
        // 565 is faster to decode and display 
        // and we don't want to dither here because the image will be scaled down later 
        sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565; 
        sBitmapOptionsCache.inDither = false
 
        sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; 
        sBitmapOptions.inDither = false
    } 
 
    public static void initAlbumArtCache() { 
        try { 
            int id = sService.getMediaMountedCount(); 
            if (id != sArtCacheId) { 
                clearAlbumArtCache(); 
                sArtCacheId = id;  
            } 
        } catch (RemoteException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    public static void clearAlbumArtCache() { 
        synchronized(sArtCache) { 
            sArtCache.clear(); 
        } 
    } 
     
    public static Drawable getCachedArtwork(Context context, long artIndex, BitmapDrawable defaultArtwork) { 
        Drawable d = null
        synchronized(sArtCache) { 
            d = sArtCache.get(artIndex); 
        } 
        if (d == null) { 
            d = defaultArtwork; 
            final Bitmap icon = defaultArtwork.getBitmap(); 
            int w = icon.getWidth(); 
            int h = icon.getHeight(); 
            Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h); 
            if (b != null) { 
                d = new FastBitmapDrawable(b); 
                synchronized(sArtCache) { 
                    // the cache may have changed since we checked 
                    Drawable value = sArtCache.get(artIndex); 
                    if (value == null) { 
                        sArtCache.put(artIndex, d); 
                    } else { 
                        d = value; 
                    } 
                } 
            } 
        } 
        return d; 
    } 
 
    // Get album art for specified album. This method will not try to 
    // fall back to getting artwork directly from the file, nor will 
    // it attempt to repair the database. 
    private static Bitmap getArtworkQuick(Context context, long album_id, int w, int h) { 
        // NOTE: There is in fact a 1 pixel border on the right side in the ImageView 
        // used to display this drawable. Take it into account now, so we don't have to 
        // scale later. 
        w -= 1
        ContentResolver res = context.getContentResolver(); 
        Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id); 
        if (uri != null) { 
            ParcelFileDescriptor fd = null
            try { 
                fd = res.openFileDescriptor(uri, "r"); 
                int sampleSize = 1
                 
                // Compute the closest power-of-two scale factor  
                // and pass that to sBitmapOptionsCache.inSampleSize, which will 
                // result in faster decoding and better quality 
                sBitmapOptionsCache.inJustDecodeBounds = true
                BitmapFactory.decodeFileDescriptor( 
                        fd.getFileDescriptor(), null, sBitmapOptionsCache); 
                int nextWidth = sBitmapOptionsCache.outWidth >> 1
                int nextHeight = sBitmapOptionsCache.outHeight >> 1
                while (nextWidth>w && nextHeight>h) { 
                    sampleSize <<= 1
                    nextWidth >>= 1
                    nextHeight >>= 1
                } 
 
                sBitmapOptionsCache.inSampleSize = sampleSize; 
                sBitmapOptionsCache.inJustDecodeBounds = false
                Bitmap b = BitmapFactory.decodeFileDescriptor( 
                        fd.getFileDescriptor(), null, sBitmapOptionsCache); 
 
                if (b != null) { 
                    // finally rescale to exactly the size we need 
                    if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) { 
                        Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true); 
                        // Bitmap.createScaledBitmap() can return the same bitmap 
                        if (tmp != b) b.recycle(); 
                        b = tmp; 
                    } 
                } 
                 
                return b; 
            } catch (FileNotFoundException e) { 
            } finally { 
                try { 
                    if (fd != null
                        fd.close(); 
                } catch (IOException e) { 
                } 
            } 
        } 
        return null
    } 
 
    /** Get album art for specified album. You should not pass in the album id
     * for the "unknown" album here (use -1 instead) 
     * This method always returns the default album art icon when no album art is found. 
     */
 
    public static Bitmap getArtwork(Context context, long song_id, long album_id) { 
        return getArtwork(context, song_id, album_id, true); 
    } 
 
    /** Get album art for specified album. You should not pass in the album id
     * for the "unknown" album here (use -1 instead) 
     */
 
    public static Bitmap getArtwork(Context context, long song_id, long album_id, 
            boolean allowdefault) { 
 
        if (album_id < 0) { 
            // This is something that is not in the database, so get the album art directly 
            // from the file. 
            if (song_id >= 0) { 
                Bitmap bm = getArtworkFromFile(context, song_id, -1); 
                if (bm != null) { 
                    return bm; 
                } 
            } 
            if (allowdefault) { 
                return getDefaultArtwork(context); 
            } 
            return null
        } 
 
        ContentResolver res = context.getContentResolver(); 
        Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id); 
        if (uri != null) { 
            InputStream in = null
            try { 
                in = res.openInputStream(uri); 
                return BitmapFactory.decodeStream(in, null, sBitmapOptions); 
            } catch (FileNotFoundException ex) { 
                // The album art thumbnail does not actually exist. Maybe the user deleted it, or 
                // maybe it never existed to begin with. 
                Bitmap bm = getArtworkFromFile(context, song_id, album_id); 
                if (bm != null) { 
                    if (bm.getConfig() == null) { 
                        bm = bm.copy(Bitmap.Config.RGB_565, false); 
                        if (bm == null && allowdefault) { 
                            return getDefaultArtwork(context); 
                        } 
                    } 
                } else if (allowdefault) { 
                    bm = getDefaultArtwork(context); 
                } 
                return bm; 
            } finally { 
                try { 
                    if (in != null) { 
                        in.close(); 
                    } 
                } catch (IOException ex) { 
                } 
            } 
        } 
         
        return null
    } 
     
    // get album art for specified file 
    private static final String sExternalMediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString(); 
    private static Bitmap getArtworkFromFile(Context context, long songid, long albumid) { 
        Bitmap bm = null
        byte [] art = null
        String path = null
 
        if (albumid < 0 && songid < 0) { 
            throw new IllegalArgumentException("Must specify an album or a song id"); 
        } 
 
        try { 
            if (albumid < 0) { 
                Uri uri = Uri.parse("content://media/external/audio/media/" + songid + "/albumart"); 
                ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r"); 
                if (pfd != null) { 
                    FileDescriptor fd = pfd.getFileDescriptor(); 
                    bm = BitmapFactory.decodeFileDescriptor(fd); 
                } 
            } else { 
                Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid); 
                ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r"); 
                if (pfd != null) { 
                    FileDescriptor fd = pfd.getFileDescriptor(); 
                    bm = BitmapFactory.decodeFileDescriptor(fd); 
                } 
            } 
        } catch (IllegalStateException ex) { 
        } catch (FileNotFoundException ex) { 
        } 
        if (bm != null) { 
            mCachedBit = bm; 
        } 
        return bm; 
    } 
     
    private static Bitmap getDefaultArtwork(Context context) { 
        BitmapFactory.Options opts = new BitmapFactory.Options(); 
        opts.inPreferredConfig = Bitmap.Config.ARGB_8888; 
        return BitmapFactory.decodeStream( 
                context.getResources().openRawResource(R.drawable.albumart_mp_unknown), null, opts); 
    } 
     
    static int getIntPref(Context context, String name, int def) { 
        SharedPreferences prefs = 
            context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); 
        return prefs.getInt(name, def); 
    } 
     
    static void setIntPref(Context context, String name, int value) { 
        SharedPreferences prefs = 
            context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); 
        Editor ed = prefs.edit(); 
        ed.putInt(name, value); 
        SharedPreferencesCompat.apply(ed); 
    } 
 
    static void setRingtone(Context context, long id) { 
        ContentResolver resolver = context.getContentResolver(); 
        // Set the flag in the database to mark this as a ringtone 
        Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); 
        try { 
            ContentValues values = new ContentValues(2); 
            values.put(MediaStore.Audio.Media.IS_RINGTONE, "1"); 
            values.put(MediaStore.Audio.Media.IS_ALARM, "1"); 
            resolver.update(ringUri, values, nullnull); 
        } catch (UnsupportedOperationException ex) { 
            // most likely the card just got unmounted 
            Log.e(TAG, "couldn't set ringtone flag for id " + id); 
            return
        } 
 
        String[] cols = new String[] { 
                MediaStore.Audio.Media._ID, 
                MediaStore.Audio.Media.DATA, 
                MediaStore.Audio.Media.TITLE 
        }; 
 
        String where = MediaStore.Audio.Media._ID + "=" + id; 
        Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                cols, where , nullnull); 
        try { 
            if (cursor != null && cursor.getCount() == 1) { 
                // Set the system setting to make this the current ringtone 
                cursor.moveToFirst(); 
                Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString()); 
                String message = context.getString(R.string.ringtone_set, cursor.getString(2)); 
                Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 
            } 
        } finally { 
            if (cursor != null) { 
                cursor.close(); 
            } 
        } 
    } 
     
    static int sActiveTabIndex = -1
     
    static boolean updateButtonBar(Activity a, int highlight) { 
        final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar); 
        boolean withtabs = false
        Intent intent = a.getIntent(); 
        if (intent != null) { 
            withtabs = intent.getBooleanExtra("withtabs"false); 
        } 
         
        if (highlight == 0 || !withtabs) { 
            ll.setVisibility(View.GONE); 
            return withtabs; 
        } else if (withtabs) { 
            ll.setVisibility(View.VISIBLE); 
        } 
        for (int i = ll.getChildCount() - 1; i >= 0; i--) { 
             
            View v = ll.getChildAt(i); 
            boolean isActive = (v.getId() == highlight); 
            if (isActive) { 
                ll.setCurrentTab(i); 
                sActiveTabIndex = i; 
            } 
            v.setTag(i); 
            v.setOnFocusChangeListener(new View.OnFocusChangeListener() { 
 
                public void onFocusChange(View v, boolean hasFocus) { 
                    if (hasFocus) { 
                        for (int i = 0; i < ll.getTabCount(); i++) { 
                            if (ll.getChildTabViewAt(i) == v) { 
                                ll.setCurrentTab(i); 
                                processTabClick((Activity)ll.getContext(), v, ll.getChildAt(sActiveTabIndex).getId()); 
                                break
                            } 
                        } 
                    } 
                }}); 
             
            v.setOnClickListener(new View.OnClickListener() { 
 
                public void onClick(View v) { 
                    processTabClick((Activity)ll.getContext(), v, ll.getChildAt(sActiveTabIndex).getId()); 
                }}); 
        } 
        return withtabs; 
    } 
 
    static void processTabClick(Activity a, View v, int current) { 
        int id = v.getId(); 
        if (id == current) { 
            return
        } 
 
        final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar); 
 
        activateTab(a, id); 
        if (id != R.id.nowplayingtab) { 
            ll.setCurrentTab((Integer) v.getTag()); 
            setIntPref(a, "activetab", id); 
        } 
    } 
     
    static void activateTab(Activity a, int id) { 
        Intent intent = new Intent(Intent.ACTION_PICK); 
        switch (id) { 
            case R.id.artisttab: 
                intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/artistalbum"); 
                break
            case R.id.albumtab: 
                intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album"); 
                break
            case R.id.songtab: 
                intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 
                break
            case R.id.playlisttab: 
                intent.setDataAndType(Uri.EMPTY, MediaStore.Audio.Playlists.CONTENT_TYPE); 
                break
            case R.id.nowplayingtab: 
                intent = new Intent(a, MediaPlaybackActivity.class); 
                a.startActivity(intent); 
                // fall through and return 
            default
                return
        } 
        intent.putExtra("withtabs"true); 
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
        a.startActivity(intent); 
        a.finish(); 
        a.overridePendingTransition(00); 
    } 
     
    static void updateNowPlaying(Activity a) { 
        View nowPlayingView = a.findViewById(R.id.nowplaying); 
        if (nowPlayingView == null) { 
            return
        } 
        try { 
            boolean withtabs = false
            Intent intent = a.getIntent(); 
            if (intent != null) { 
                withtabs = intent.getBooleanExtra("withtabs"false); 
            } 
            if (true && MusicUtils.sService != null && MusicUtils.sService.getAudioId() != -1) { 
                TextView title = (TextView) nowPlayingView.findViewById(R.id.title); 
                TextView artist = (TextView) nowPlayingView.findViewById(R.id.artist); 
                title.setText(MusicUtils.sService.getTrackName()); 
                String artistName = MusicUtils.sService.getArtistName(); 
                if (MediaStore.UNKNOWN_STRING.equals(artistName)) { 
                    artistName = a.getString(R.string.unknown_artist_name); 
                } 
                artist.setText(artistName); 
                //mNowPlayingView.setOnFocusChangeListener(mFocuser); 
                //mNowPlayingView.setOnClickListener(this); 
                nowPlayingView.setVisibility(View.VISIBLE); 
                nowPlayingView.setOnClickListener(new View.OnClickListener() { 
 
                    public void onClick(View v) { 
                        Context c = v.getContext(); 
                        c.startActivity(new Intent(c, MediaPlaybackActivity.class)); 
                    }}); 
                return
            } 
        } catch (RemoteException ex) { 
        } 
        nowPlayingView.setVisibility(View.GONE); 
    } 
 
    static void setBackground(View v, Bitmap bm) { 
 
        if (bm == null) { 
            v.setBackgroundResource(0); 
            return
        } 
 
        int vwidth = v.getWidth(); 
        int vheight = v.getHeight(); 
        int bwidth = bm.getWidth(); 
        int bheight = bm.getHeight(); 
        float scalex = (float) vwidth / bwidth; 
        float scaley = (float) vheight / bheight; 
        float scale = Math.max(scalex, scaley) * 1.3f
 
        Bitmap.Config config = Bitmap.Config.ARGB_8888; 
        Bitmap bg = Bitmap.createBitmap(vwidth, vheight, config); 
        Canvas c = new Canvas(bg); 
        Paint paint = new Paint(); 
        paint.setAntiAlias(true); 
        paint.setFilterBitmap(true); 
        ColorMatrix greymatrix = new ColorMatrix(); 
        greymatrix.setSaturation(0); 
        ColorMatrix darkmatrix = new ColorMatrix(); 
        darkmatrix.setScale(.3f.3f.3f1.0f); 
        greymatrix.postConcat(darkmatrix); 
        ColorFilter filter = new ColorMatrixColorFilter(greymatrix); 
        paint.setColorFilter(filter); 
        Matrix matrix = new Matrix(); 
        matrix.setTranslate(-bwidth/2, -bheight/2); // move bitmap center to origin 
        matrix.postRotate(10); 
        matrix.postScale(scale, scale); 
        matrix.postTranslate(vwidth/2, vheight/2);  // Move bitmap center to view center 
        c.drawBitmap(bm, matrix, paint); 
        v.setBackgroundDrawable(new BitmapDrawable(bg)); 
    } 
 
    static int getCardId(Context context) { 
        ContentResolver res = context.getContentResolver(); 
        Cursor c = res.query(Uri.parse("content://media/external/fs_id"), nullnullnullnull); 
        int id = -1
        if (c != null) { 
            c.moveToFirst(); 
            id = c.getInt(0); 
            c.close(); 
        } 
        return id; 
    } 
 
    static class LogEntry { 
        Object item; 
        long time; 
 
        LogEntry(Object o) { 
            item = o; 
            time = System.currentTimeMillis(); 
        } 
 
        void dump(PrintWriter out) { 
            sTime.set(time); 
            out.print(sTime.toString() + " : "); 
            if (item instanceof Exception) { 
                ((Exception)item).printStackTrace(out); 
            } else { 
                out.println(item); 
            } 
        } 
    } 
 
    private static LogEntry[] sMusicLog = new LogEntry[100]; 
    private static int sLogPtr = 0
    private static Time sTime = new Time(); 
 
    static void debugLog(Object o) { 
 
        sMusicLog[sLogPtr] = new LogEntry(o); 
        sLogPtr++; 
        if (sLogPtr >= sMusicLog.length) { 
            sLogPtr = 0
        } 
    } 
 
    static void debugDump(PrintWriter out) { 
        for (int i = 0; i < sMusicLog.length; i++) { 
            int idx = (sLogPtr + i); 
            if (idx >= sMusicLog.length) { 
                idx -= sMusicLog.length; 
            } 
            LogEntry entry = sMusicLog[idx]; 
            if (entry != null) { 
                entry.dump(out); 
            } 
        } 
    } 
}