github.madmarty.madsonic.util.Util.java Source code

Java tutorial

Introduction

Here is the source code for github.madmarty.madsonic.util.Util.java

Source

/*
 This file is part of Subsonic.
    
 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 Subsonic is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
    
 Copyright 2009 (C) Sindre Mehus
 */
package github.madmarty.madsonic.util;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.KeyEvent;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
import android.widget.Toast;
import github.madmarty.madsonic.R;
import github.madmarty.madsonic.activity.DownloadActivity;
import github.madmarty.madsonic.activity.SubsonicTabActivity;
import github.madmarty.madsonic.domain.MusicDirectory;
import github.madmarty.madsonic.domain.MusicDirectory.Entry;
import github.madmarty.madsonic.domain.MadsonicTheme;
import github.madmarty.madsonic.domain.PlayerState;
import github.madmarty.madsonic.domain.RepeatMode;
import github.madmarty.madsonic.domain.SearchResult;
import github.madmarty.madsonic.domain.Version;
import github.madmarty.madsonic.provider.MadsonicWidgetProvider;
import github.madmarty.madsonic.receiver.MediaButtonIntentReceiver;
import github.madmarty.madsonic.service.DownloadService;
import github.madmarty.madsonic.service.DownloadServiceImpl;
import github.madmarty.madsonic.service.MusicService;
import github.madmarty.madsonic.service.MusicServiceFactory;
import github.madmarty.madsonic.service.OfflineException;
import github.madmarty.madsonic.service.ServerTooOldException;

import org.apache.http.HttpEntity;

/**
 * @author Sindre Mehus
 * @version $Id$
 */
public final class Util {

    private static final Logger LOG = new Logger(Util.class);

    private static final DecimalFormat GIGA_BYTE_FORMAT = new DecimalFormat("0.00 GB");
    private static final DecimalFormat MEGA_BYTE_FORMAT = new DecimalFormat("0.00 MB");
    private static final DecimalFormat KILO_BYTE_FORMAT = new DecimalFormat("0 KB");

    private static DecimalFormat GIGA_BYTE_LOCALIZED_FORMAT = null;
    private static DecimalFormat MEGA_BYTE_LOCALIZED_FORMAT = null;
    private static DecimalFormat KILO_BYTE_LOCALIZED_FORMAT = null;
    private static DecimalFormat BYTE_LOCALIZED_FORMAT = null;

    public static final String EVENT_META_CHANGED = "github.madmarty.madsonic.EVENT_META_CHANGED";
    public static final String EVENT_PLAYSTATE_CHANGED = "github.madmarty.madsonic.EVENT_PLAYSTATE_CHANGED";

    public static final String AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
    public static final String AVRCP_METADATA_CHANGED = "com.android.music.metachanged";

    private static boolean hasFocus = false;
    private static boolean pauseFocus = false;
    private static boolean lowerFocus = false;

    private static final Map<Integer, Version> SERVER_REST_VERSIONS = new ConcurrentHashMap<Integer, Version>();

    // Used by hexEncode()
    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
            'e', 'f' };

    private final static Pair<Integer, Integer> NOTIFICATION_TEXT_COLORS = new Pair<Integer, Integer>();
    private static Toast toast;

    private Util() {
    }

    public static boolean isOffline(Context context) {
        if (context == null)
            return false;
        else
            return getActiveServer(context) == 0;
    }

    public static boolean isScreenLitOnDownload(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD, false);
    }

    public static RepeatMode getRepeatMode(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return RepeatMode.valueOf(prefs.getString(Constants.PREFERENCES_KEY_REPEAT_MODE, RepeatMode.OFF.name()));
    }

    public static void setRepeatMode(Context context, RepeatMode repeatMode) {
        SharedPreferences prefs = getPreferences(context);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(Constants.PREFERENCES_KEY_REPEAT_MODE, repeatMode.name());
        editor.commit();
    }

    public static boolean isScrobblingEnabled(Context context) {
        if (isOffline(context)) {
            return false;
        }
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false);
    }

    public static void setActiveServer(Context context, int instance) {
        SharedPreferences prefs = getPreferences(context);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
        editor.commit();
    }

    public static int getActiveServer(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
    }

    public static int getActiveServers(Context context) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
        return settings.getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 0);
    }

    public static boolean getServerEnabled(Context context, int instance) {
        if (instance == 0) {
            return true;
        }
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + instance, true);
    }

    public static boolean isStreamProxyEnabled(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_USE_STREAM_PROXY, true);
    }

    public static void removeInstanceName(Context context, int instance) {
        SharedPreferences prefs = getPreferences(context);
        SharedPreferences.Editor editor = prefs.edit();

        editor.putString(Constants.PREFERENCES_KEY_SERVER + instance, null);
        editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
        editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
        editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
        editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
        editor.putBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + instance, true);
        editor.commit();
    }

    public static void removeInstanceName(Context context, int instance, int activeInstance) {
        SharedPreferences prefs = getPreferences(context);
        SharedPreferences.Editor editor = prefs.edit();

        int newInstance = instance + 1;

        String server = prefs.getString(Constants.PREFERENCES_KEY_SERVER + newInstance, null);
        String serverName = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
        String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
        String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
        String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
        boolean serverEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + newInstance, true);

        editor.putString(Constants.PREFERENCES_KEY_SERVER + instance, server);
        editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName);
        editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl);
        editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName);
        editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password);
        editor.putBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + instance, serverEnabled);

        editor.putString(Constants.PREFERENCES_KEY_SERVER + newInstance, null);
        editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
        editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
        editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
        editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
        editor.putBoolean(Constants.PREFERENCES_KEY_SERVER_ENABLED + newInstance, true);
        editor.commit();

        if (instance == activeInstance) {
            Util.setActiveServer(context, 0);
        }

        if (newInstance == activeInstance) {
            Util.setActiveServer(context, instance);
        }
    }

    public static String getServerName(Context context) {
        SharedPreferences prefs = getPreferences(context);
        int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
        return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
    }

    //    public static String getServerName(Context context, int instance) {
    //        SharedPreferences prefs = getPreferences(context);
    //        return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
    //    }

    public static String getServerName(Context context, int instance) {
        if (instance == 0) {
            return context.getResources().getString(R.string.main_offline);
        }
        SharedPreferences prefs = getPreferences(context);
        return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
    }

    public static String getUserName(Context context, int instance) {
        if (instance == 0) {
            return context.getResources().getString(R.string.main_offline);
        }
        SharedPreferences prefs = getPreferences(context);
        return prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
    }

    //    public static void setServerRestVersion(Context context, Version version) {
    //        SERVER_REST_VERSIONS.put(getActiveServer(context), version);
    //    }
    //
    //    public static Version getServerRestVersion(Context context) {
    //        return SERVER_REST_VERSIONS.get(getActiveServer(context));
    //    }   

    public static void setServerRestVersion(Context context, Version version) {
        int instance = getActiveServer(context);
        Version current = SERVER_REST_VERSIONS.get(instance);
        if (current != version) {
            SERVER_REST_VERSIONS.put(instance, version);
            SharedPreferences.Editor editor = getPreferences(context).edit();
            editor.putString(Constants.PREFERENCES_KEY_SERVER_VERSION + instance, version.toString());
            editor.commit();
        }
    }

    public static Version getServerRestVersion(Context context) {
        int instance = getActiveServer(context);
        Version version = SERVER_REST_VERSIONS.get(instance);
        if (version == null) {
            SharedPreferences prefs = getPreferences(context);
            String versionString = prefs.getString(Constants.PREFERENCES_KEY_SERVER_VERSION + instance, null);
            if (versionString != null && versionString != "") {
                version = new Version(versionString);
                SERVER_REST_VERSIONS.put(instance, version);
            }
        }
        return version;
    }

    public static void setSelectedMusicFolderId(Context context, String musicFolderId) {
        int instance = getActiveServer(context);
        SharedPreferences prefs = getPreferences(context);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId);
        editor.commit();
    }

    public static String getSelectedMusicFolderId(Context context) {
        SharedPreferences prefs = getPreferences(context);
        int instance = getActiveServer(context);
        return prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null);
    }

    public static String getTheme(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getString(Constants.PREFERENCES_KEY_THEME, "Madsonic Dark");
    }

    public static boolean getThemeFullscreen(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_THEME_FULLSCREEN, false);
    }

    public static boolean jump2playlist(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_JUMP2PLAYLIST, false);
    }

    public static int getCoverSize(Context context) {
        SharedPreferences prefs = getPreferences(context);
        int coverSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_COVER_SIZE, "128"));
        return coverSize;
    }

    public static boolean getDisplayTrack(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_DISPLAY_TRACK, false);
    }

    public static int getMaxBitrate(Context context) {
        ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();
        if (networkInfo == null) {
            return 0;
        }

        boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(
                wifi ? Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE,
                "0"));
    }

    public static int getMaxVideoBitrate(Context context) {
        ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();
        if (networkInfo == null) {
            return 0;
        }

        boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI
                : Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE, "0"));
    }

    public static boolean shouldDisplayBitrateWithArtist(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_DISPLAY_BITRATE_WITH_ARTIST, false);
    }

    public static boolean shouldDisplayDurationWithSong(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_DISPLAY_DURATION_WITH_SONG, false);
    }

    public static boolean shouldDisplayRankWithSong(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_DISPLAY_RANK_WITH_SONG, true);
    }

    public static int getDefaultVideoplayer(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_DEFAULT_VIDEOPLAYER, "1"));
    }

    public static int getPreloadCount(Context context) {
        SharedPreferences prefs = getPreferences(context);
        int preloadCount = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_PRELOAD_COUNT, "-1"));
        return preloadCount == -1 ? Integer.MAX_VALUE : preloadCount;
    }

    public static VideoPlayerType getVideoPlayerType(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return VideoPlayerType
                .forKey(prefs.getString(Constants.PREFERENCES_KEY_VIDEO_PLAYER, VideoPlayerType.MX.getKey()));
    }

    public static int getCacheSizeMB(Context context) {
        SharedPreferences prefs = getPreferences(context);
        int cacheSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CACHE_SIZE, "-1"));
        return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize;
    }

    public static String getRestUrl(Context context, String method) {
        StringBuilder builder = new StringBuilder();

        SharedPreferences prefs = getPreferences(context);

        int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
        return getRestUrl(context, method, prefs, instance);
    }

    public static String getRestUrl(Context context, String method, int instance) {
        SharedPreferences prefs = getPreferences(context);
        return getRestUrl(context, method, prefs, instance);
    }

    public static String getRestUrl(Context context, String method, SharedPreferences prefs, int instance) {
        StringBuilder builder = new StringBuilder();

        String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
        String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
        String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);

        // Slightly obfuscate password
        password = "enc:" + Util.utf8HexEncode(password);

        builder.append(serverUrl);
        if (builder.charAt(builder.length() - 1) != '/') {
            builder.append("/");
        }
        builder.append("rest/").append(method).append(".view");
        builder.append("?u=").append(username);
        builder.append("&p=").append(password);
        builder.append("&v=").append(Constants.REST_PROTOCOL_VERSION);
        builder.append("&c=").append(Constants.REST_CLIENT_ID);

        return builder.toString();
    }

    public static SharedPreferences getPreferences(Context context) {
        return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0);
    }

    public static String getContentType(HttpEntity entity) {
        if (entity == null || entity.getContentType() == null) {
            return null;
        }
        return entity.getContentType().getValue();
    }

    /**
     * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
     * <p/>
     * This method buffers the input internally, so there is no need to use a
     * <code>BufferedInputStream</code>.
     *
     * @param input the <code>InputStream</code> to read from
     * @return the requested byte array
     * @throws NullPointerException if the input is null
     * @throws IOException          if an I/O error occurs
     */
    public static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        copy(input, output);
        return output.toByteArray();
    }

    public static long copy(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[1024 * 4];
        long count = 0;
        int n;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }

    public static void atomicCopy(File from, File to) throws IOException {
        FileInputStream in = null;
        FileOutputStream out = null;
        File tmp = null;
        try {
            tmp = new File(to.getPath() + ".tmp");
            in = new FileInputStream(from);
            out = new FileOutputStream(tmp);
            in.getChannel().transferTo(0, from.length(), out.getChannel());
            out.close();
            if (!tmp.renameTo(to)) {
                throw new IOException("Failed to rename " + tmp + " to " + to);
            }
            LOG.info("Copied " + from + " to " + to);
        } catch (IOException x) {
            close(out);
            delete(to);
            throw x;
        } finally {
            close(in);
            close(out);
            delete(tmp);
        }
    }

    public static void renameFile(File from, File to) throws IOException {
        if (from.renameTo(to)) {
            LOG.info("Renamed " + from + " to " + to);
        } else {
            atomicCopy(from, to);
        }
    }

    public static void close(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (Throwable x) {
            // Ignored
        }
    }

    public static boolean delete(File file) {
        if (file != null && file.exists()) {
            if (!file.delete()) {
                LOG.warn("Failed to delete file " + file);
                return false;
            }
            LOG.info("Deleted file " + file);
        }
        return true;
    }

    public static boolean recursiveDelete(File dir) {
        if (dir != null && dir.exists()) {
            for (File file : dir.listFiles()) {
                if (file.isDirectory()) {
                    if (!recursiveDelete(file)) {
                        return false;
                    }
                } else if (file.exists()) {
                    if (!file.delete()) {
                        return false;
                    }
                }
            }
            return dir.delete();
        }
        return false;
    }

    public static void toast(Context context, int messageId) {
        toast(context, messageId, true);
    }

    public static void toast(Context context, int messageId, boolean shortDuration, Object... formatArgs) {
        toast(context, context.getString(messageId, formatArgs), shortDuration);
    }

    public static void toast(Context context, String message) {
        toast(context, message, true);
    }

    public static void toast(Context context, String message, boolean shortDuration) {
        if (toast == null) {
            toast = Toast.makeText(context, message, shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
            toast.setGravity(Gravity.CENTER, 0, 0);
        } else {
            toast.setText(message);
            toast.setDuration(shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
        }
        toast.show();
    }

    /**
     * Converts a byte-count to a formatted string suitable for display to the user.
     * For instance:
     * <ul>
     * <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
     * <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
     * <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
     * </ul>
     * This method assumes that 1 KB is 1024 bytes.
     * To get a localized string, please use formatLocalizedBytes instead.
     *
     * @param byteCount The number of bytes.
     * @return The formatted string.
     */
    public static synchronized String formatBytes(long byteCount) {

        // More than 1 GB?
        if (byteCount >= 1024 * 1024 * 1024) {
            NumberFormat gigaByteFormat = GIGA_BYTE_FORMAT;
            return gigaByteFormat.format((double) byteCount / (1024 * 1024 * 1024));
        }

        // More than 1 MB?
        if (byteCount >= 1024 * 1024) {
            NumberFormat megaByteFormat = MEGA_BYTE_FORMAT;
            return megaByteFormat.format((double) byteCount / (1024 * 1024));
        }

        // More than 1 KB?
        if (byteCount >= 1024) {
            NumberFormat kiloByteFormat = KILO_BYTE_FORMAT;
            return kiloByteFormat.format((double) byteCount / 1024);
        }

        return byteCount + " B";
    }

    /**
     * Converts a byte-count to a formatted string suitable for display to the user.
     * For instance:
     * <ul>
     * <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
     * <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
     * <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
     * </ul>
     * This method assumes that 1 KB is 1024 bytes.
     * This version of the method returns a localized string.
     *
     * @param byteCount The number of bytes.
     * @return The formatted string.
     */
    public static synchronized String formatLocalizedBytes(long byteCount, Context context) {

        // More than 1 GB?
        if (byteCount >= 1024 * 1024 * 1024) {
            if (GIGA_BYTE_LOCALIZED_FORMAT == null) {
                GIGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(
                        context.getResources().getString(R.string.util_bytes_format_gigabyte));
            }

            return GIGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024 * 1024));
        }

        // More than 1 MB?
        if (byteCount >= 1024 * 1024) {
            if (MEGA_BYTE_LOCALIZED_FORMAT == null) {
                MEGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(
                        context.getResources().getString(R.string.util_bytes_format_megabyte));
            }

            return MEGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024));
        }

        // More than 1 KB?
        if (byteCount >= 1024) {
            if (KILO_BYTE_LOCALIZED_FORMAT == null) {
                KILO_BYTE_LOCALIZED_FORMAT = new DecimalFormat(
                        context.getResources().getString(R.string.util_bytes_format_kilobyte));
            }

            return KILO_BYTE_LOCALIZED_FORMAT.format((double) byteCount / 1024);
        }

        if (BYTE_LOCALIZED_FORMAT == null) {
            BYTE_LOCALIZED_FORMAT = new DecimalFormat(
                    context.getResources().getString(R.string.util_bytes_format_byte));
        }

        return BYTE_LOCALIZED_FORMAT.format((double) byteCount);
    }

    public static String formatDuration(Integer seconds) {
        if (seconds == null) {
            return null;
        }

        int hours = seconds / 3600;
        int minutes = (seconds / 60) % 60;
        int secs = seconds % 60;

        StringBuilder builder = new StringBuilder(7);
        if (hours > 0) {
            builder.append(hours).append(":");
            if (minutes < 10) {
                builder.append("0");
            }
        }
        builder.append(minutes).append(":");
        if (secs < 10) {
            builder.append("0");
        }
        builder.append(secs);
        return builder.toString();
    }

    public static boolean equals(Object object1, Object object2) {
        if (object1 == object2) {
            return true;
        }
        if (object1 == null || object2 == null) {
            return false;
        }
        return object1.equals(object2);

    }

    /**
     * Encodes the given string by using the hexadecimal representation of its UTF-8 bytes.
     *
     * @param s The string to encode.
     * @return The encoded string.
     */
    public static String utf8HexEncode(String s) {
        if (s == null) {
            return null;
        }
        byte[] utf8;
        try {
            utf8 = s.getBytes(Constants.UTF_8);
        } catch (UnsupportedEncodingException x) {
            throw new RuntimeException(x);
        }
        return hexEncode(utf8);
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     * The returned array will be double the length of the passed array, as it takes two characters to represent any
     * given byte.
     *
     * @param data Bytes to convert to hexadecimal characters.
     * @return A string containing hexadecimal characters.
     */
    public static String hexEncode(byte[] data) {
        int length = data.length;
        char[] out = new char[length << 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; i < length; i++) {
            out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
            out[j++] = HEX_DIGITS[0x0F & data[i]];
        }
        return new String(out);
    }

    /**
     * Calculates the MD5 digest and returns the value as a 32 character hex string.
     *
     * @param s Data to digest.
     * @return MD5 digest as a hex string.
     */
    public static String md5Hex(String s) {
        if (s == null) {
            return null;
        }

        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            return hexEncode(md5.digest(s.getBytes(Constants.UTF_8)));
        } catch (Exception x) {
            throw new RuntimeException(x.getMessage(), x);
        }
    }

    public static String decrypt(String s) {
        if (s == null) {
            return null;
        }
        if (!s.startsWith("enc:")) {
            return s;
        }
        try {
            //return utf8HexDecode(s.substring(4));
            return hexToString(s.substring(4));

        } catch (Exception e) {
            return s;
        }
    }

    public static String hexToString(final String str) {
        return new String(new BigInteger(str, 16).toByteArray());
    }

    //    public static String utf8HexDecode(String s) throws Exception {
    //        if (s == null) {
    //            return null;
    //        }
    //        return new String(Hex.decodeHex(s.toCharArray()), ENCODING_UTF8);
    //    }    

    public static boolean isNetworkConnected(Context context) {
        ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();
        boolean connected = networkInfo != null && networkInfo.isConnected();

        boolean wifiConnected = connected && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
        boolean wifiRequired = isWifiRequiredForDownload(context);

        return connected && (!wifiRequired || wifiConnected);
    }

    public static boolean isExternalStoragePresent() {
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }

    private static boolean isWifiRequiredForDownload(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD, false);
    }

    public static void info(Context context, int titleId, int messageId) {
        showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId);
    }

    public static void info(Context context, int titleId, String message) {
        showDialog(context, android.R.drawable.ic_dialog_info, titleId, message);
    }

    private static void showDialog(Context context, int icon, int titleId, int messageId) {
        new AlertDialog.Builder(context).setIcon(icon).setTitle(titleId).setMessage(messageId)
                .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int i) {
                        dialog.dismiss();
                    }
                }).show();
    }

    private static void showDialog(Context context, int icon, int titleId, String message) {
        new AlertDialog.Builder(context).setIcon(icon).setTitle(titleId).setMessage(message)
                .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int i) {
                        dialog.dismiss();
                    }
                }).show();
    }

    private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean playing) {

        // Use the same text for the ticker and the expanded notification
        String title = song.getTitle();
        String arist = song.getArtist();
        String album = song.getAlbum();

        // Set the album art.
        try {
            int size = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
            Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, size);
            if (bitmap == null) {
                // set default album art
                rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
            } else {
                rv.setImageViewBitmap(R.id.notification_image, bitmap);
            }
        } catch (Exception x) {
            LOG.warn("Failed to get notification cover art", x);
            rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
        }

        rv.setImageViewResource(R.id.control_starred,
                song.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);

        // set the text for the notifications
        rv.setTextViewText(R.id.notification_title, title);
        rv.setTextViewText(R.id.notification_artist, arist);
        rv.setTextViewText(R.id.notification_album, album);

        Pair<Integer, Integer> colors = getNotificationTextColors(context);
        if (colors.getFirst() != null) {
            rv.setTextColor(R.id.notification_title, colors.getFirst());
        }
        if (colors.getSecond() != null) {
            rv.setTextColor(R.id.notification_artist, colors.getSecond());
        }

        if (!playing) {
            rv.setImageViewResource(R.id.control_pause, R.drawable.notification_play);
            rv.setImageViewResource(R.id.control_previous, R.drawable.notification_stop);
        }

        // Create actions for media buttons
        PendingIntent pendingIntent;
        if (playing) {
            Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS");
            prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
            prevIntent.putExtra(Intent.EXTRA_KEY_EVENT,
                    new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
            pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
            rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
        } else {
            Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP");
            prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
            prevIntent.putExtra(Intent.EXTRA_KEY_EVENT,
                    new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP));
            pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
            rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
        }

        Intent starredIntent = new Intent("KEYCODE_MEDIA_STARRED");
        starredIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
        starredIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_STAR));
        pendingIntent = PendingIntent.getService(context, 0, starredIntent, 0);
        rv.setOnClickPendingIntent(R.id.control_starred, pendingIntent);

        Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE");
        pauseIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
        pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT,
                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
        pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0);
        rv.setOnClickPendingIntent(R.id.control_pause, pendingIntent);

        Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT");
        nextIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
        nextIntent.putExtra(Intent.EXTRA_KEY_EVENT,
                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
        pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0);
        rv.setOnClickPendingIntent(R.id.control_next, pendingIntent);
    }

    private static boolean useSimpleNotification() {
        return Build.VERSION.SDK_INT < 11;
    }

    private static Notification createSimpleNotification(Context context, MusicDirectory.Entry song) {
        Bitmap albumArt;
        try {
            albumArt = FileUtil.getAlbumArtBitmap(context, song, (int) Util.convertDpToPixel(64.0F, context));
            if (albumArt == null) {
                albumArt = Util.decodeBitmap(context, R.drawable.unknown_album);
            }
        } catch (Exception x) {
            LOG.warn("Failed to get notification cover art", x);
            albumArt = Util.decodeBitmap(context, R.drawable.unknown_album);
        }
        Intent notificationIntent = new Intent(context, DownloadActivity.class);
        return new NotificationCompat.Builder(context).setOngoing(true).setSmallIcon(R.drawable.stat_notify_playing)
                .setContentTitle(song.getTitle()).setContentText(song.getArtist())
                .setContentIntent(PendingIntent.getActivity(context, 0, notificationIntent, 0))
                .setLargeIcon(albumArt).build();
    }

    private static Notification createCustomNotification(Context context, MusicDirectory.Entry song,
            boolean playing) {
        Intent notificationIntent = new Intent(context, DownloadActivity.class);
        Notification notification = new NotificationCompat.Builder(context).setOngoing(true)
                .setSmallIcon(R.drawable.stat_notify_playing)
                //                .setContent(contentView)
                .setContentIntent(PendingIntent.getActivity(context, 0, notificationIntent, 0)).build();
        if (Build.VERSION.SDK_INT >= 16) {
            //            notification.bigContentView = createBigContentView(context, song, albumArt, playing);
        }
        return notification;
    }

    public static void showPlayingNotification(final Context context, final DownloadServiceImpl downloadService,
            Handler handler, MusicDirectory.Entry song) {

        // Set the icon, scrolling text and timestamp
        final Notification notification = new Notification(R.drawable.stat_notify, song.getTitle(),
                System.currentTimeMillis());
        notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;

        boolean playing = downloadService.getPlayerState() == PlayerState.STARTED;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            RemoteViews expandedContentView = new RemoteViews(context.getPackageName(),
                    R.layout.notification_expanded);
            setupViews(expandedContentView, context, song, playing);
            notification.bigContentView = expandedContentView;
        }

        RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification);
        setupViews(smallContentView, context, song, playing);
        notification.contentView = smallContentView;

        Intent notificationIntent = new Intent(context, DownloadActivity.class);
        notification.contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);

        handler.post(new Runnable() {
            @Override
            public void run() {
                downloadService.startForeground(Constants.NOTIFICATION_ID_PLAYING, notification);
            }
        });

        // Update widget
        MadsonicWidgetProvider.notifyInstances(context, downloadService, true);
    }

    public static void updateNotification(final Context context, final DownloadServiceImpl downloadService,
            Handler handler, MusicDirectory.Entry song, boolean playing) {

        // On older platforms, show a notification without buttons.
        if (useSimpleNotification()) {
            updateSimpleNotification(context, downloadService, handler, song, playing);
        } else {
            updateCustomNotification(context, downloadService, handler, song, playing);
        }

        // Update widget
        MadsonicWidgetProvider.notifyInstances(context, downloadService, playing);
    }

    private static void updateSimpleNotification(Context context, final DownloadServiceImpl downloadService,
            Handler handler, MusicDirectory.Entry song, boolean playing) {

        if (song == null || !playing) {
            hidePlayingNotification(context, downloadService, handler);
        } else {
            final Notification notification = createSimpleNotification(context, song);

            // Send the notification and put the service in the foreground.
            handler.post(new Runnable() {
                @Override
                public void run() {
                    downloadService.startForeground(Constants.NOTIFICATION_ID_PLAYING, notification);
                }
            });
        }
    }

    private static void updateCustomNotification(Context context, final DownloadServiceImpl downloadService,
            Handler handler, MusicDirectory.Entry song, boolean playing) {

        if (song == null) {
            hidePlayingNotification(context, downloadService, handler);

            //        } else if (!isNotificationHiddenByUser(context)) {
            final Notification notification = createCustomNotification(context, song, playing);

            // Send the notification and put the service in the foreground.
            handler.post(new Runnable() {
                @Override
                public void run() {
                    downloadService.startForeground(Constants.NOTIFICATION_ID_PLAYING, notification);
                }
            });
        }
    }

    public static void hidePlayingNotification(final Context context, final DownloadServiceImpl downloadService,
            Handler handler) {
        // Remove notification and remove the service from the foreground
        handler.post(new Runnable() {
            @Override
            public void run() {
                downloadService.stopForeground(true);
            }
        });

        // Update widget
        MadsonicWidgetProvider.notifyInstances(context, downloadService, false);
    }

    public static boolean isPackageInstalled(Context context, String packageName) {
        PackageManager pm = context.getPackageManager();
        List<ApplicationInfo> packages = pm.getInstalledApplications(0);
        for (ApplicationInfo packageInfo : packages) {
            if (packageInfo.packageName.equals(packageName)) {
                return true;
            }
        }
        return false;
    }

    public static void sleepQuietly(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException x) {
            LOG.warn("Interrupted from sleep.", x);
        }
    }

    public static void startActivityWithoutTransition(Activity currentActivity,
            Class<? extends Activity> newActivitiy) {
        startActivityWithoutTransition(currentActivity, new Intent(currentActivity, newActivitiy));
    }

    public static void startActivityWithoutTransition(Activity currentActivity, Intent intent) {
        currentActivity.startActivity(intent);
        disablePendingTransition(currentActivity);
    }

    public static void disablePendingTransition(Activity activity) {

        // Activity.overridePendingTransition() was introduced in Android 2.0.  Use reflection to maintain
        // compatibility with 1.5.
        try {
            Method method = Activity.class.getMethod("overridePendingTransition", int.class, int.class);
            method.invoke(activity, 0, 0);
        } catch (Throwable x) {
            // Ignored
        }
    }

    @SuppressWarnings("deprecation")
    public static Drawable createDrawableFromBitmap(Context context, Bitmap bitmap) {
        // BitmapDrawable(Resources, Bitmap) was introduced in Android 1.6.  Use reflection to maintain
        // compatibility with 1.5.
        try {
            Constructor<BitmapDrawable> constructor = BitmapDrawable.class.getConstructor(Resources.class,
                    Bitmap.class);
            return constructor.newInstance(context.getResources(), bitmap);
        } catch (Throwable x) {
            return new BitmapDrawable(bitmap);
        }
    }

    public static Bitmap createBitmapFromDrawable(Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static int getScaledHeight(Bitmap bitmap, int width) {
        return getScaledHeight((double) bitmap.getHeight(), (double) bitmap.getWidth(), width);
    }

    public static int getScaledHeight(double height, double width, int newWidth) {
        // Try to keep correct aspect ratio of the original image, do not force a square
        double aspectRatio = height / width;

        // Assume the size given refers to the width of the image, so calculate the new height using
        //   the previously determined aspect ratio
        return (int) Math.round(newWidth * aspectRatio);
    }

    public static Bitmap scaleBitmap(Bitmap bitmap, int size) {
        return Bitmap.createScaledBitmap(bitmap, size, getScaledHeight(bitmap, size), true);
    }

    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            // Calculate ratios of height and width to requested height and
            // width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as inSampleSize value, this will
            // guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static void registerMediaButtonEventReceiver(Context context) {

        // Only do it if enabled in the settings.
        SharedPreferences prefs = getPreferences(context);
        boolean enabled = prefs.getBoolean(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true);

        if (enabled) {

            // AudioManager.registerMediaButtonEventReceiver() was introduced in Android 2.2.
            // Use reflection to maintain compatibility with 1.5.
            try {
                AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
                ComponentName componentName = new ComponentName(context.getPackageName(),
                        MediaButtonIntentReceiver.class.getName());
                Method method = AudioManager.class.getMethod("registerMediaButtonEventReceiver",
                        ComponentName.class);
                method.invoke(audioManager, componentName);
            } catch (Throwable x) {
                // Ignored.
            }
        }
    }

    public static void unregisterMediaButtonEventReceiver(Context context) {
        // AudioManager.unregisterMediaButtonEventReceiver() was introduced in Android 2.2.
        // Use reflection to maintain compatibility with 1.5.
        try {
            AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
            ComponentName componentName = new ComponentName(context.getPackageName(),
                    MediaButtonIntentReceiver.class.getName());
            Method method = AudioManager.class.getMethod("unregisterMediaButtonEventReceiver", ComponentName.class);
            method.invoke(audioManager, componentName);
        } catch (Throwable x) {
            // Ignored.
        }
    }

    @TargetApi(8)
    public static void requestAudioFocus(final Context context) {
        if (Build.VERSION.SDK_INT >= 8 && !hasFocus) {
            final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
            hasFocus = true;
            audioManager.requestAudioFocus(new OnAudioFocusChangeListener() {
                public void onAudioFocusChange(int focusChange) {
                    DownloadServiceImpl downloadService = (DownloadServiceImpl) context;
                    if ((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
                            || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)
                            && !downloadService.isJukeboxEnabled()) {
                        if (downloadService.getPlayerState() == PlayerState.STARTED) {
                            SharedPreferences prefs = getPreferences(context);
                            int lossPref = Integer
                                    .parseInt(prefs.getString(Constants.PREFERENCES_KEY_AUDIO_FOCUS, "1"));
                            if (lossPref == 2 || (lossPref == 1
                                    && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) {
                                lowerFocus = true;
                                downloadService.setVolume(0.1f);
                            } else if (lossPref == 0
                                    || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)) {
                                pauseFocus = true;
                                downloadService.pause();
                            }
                        }
                    } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
                        if (pauseFocus) {
                            pauseFocus = false;
                            downloadService.start();
                        } else if (lowerFocus) {
                            lowerFocus = false;
                            downloadService.setVolume(1.0f);
                        }
                    } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS && !downloadService.isJukeboxEnabled()) {
                        hasFocus = false;
                        downloadService.pause();
                        audioManager.abandonAudioFocus(this);
                    }
                }
            }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
        }
    }

    public static MusicDirectory getSongsFromSearchResult(SearchResult searchResult) {
        MusicDirectory musicDirectory = new MusicDirectory();

        for (Entry entry : searchResult.getSongs()) {
            musicDirectory.addChild(entry);
        }

        return musicDirectory;
    }

    /**
     * <p>Broadcasts the given song info as the new song being played.</p>
     */
    public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) {
        DownloadService downloadService = (DownloadService) context;
        Intent intent = new Intent(EVENT_META_CHANGED);
        Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED);

        if (song != null) {
            intent.putExtra("title", song.getTitle());
            intent.putExtra("artist", song.getArtist());
            intent.putExtra("album", song.getAlbum());

            File albumArtFile = FileUtil.getAlbumArtFile(context, song);
            intent.putExtra("coverart", albumArtFile.getAbsolutePath());
            avrcpIntent.putExtra("playing", true);

        } else {
            intent.putExtra("title", "");
            intent.putExtra("artist", "");
            intent.putExtra("album", "");
            intent.putExtra("coverart", "");
            avrcpIntent.putExtra("playing", false);
        }

        addTrackInfo(context, song, avrcpIntent);

        context.sendBroadcast(intent);
        context.sendBroadcast(avrcpIntent);
    }

    /**
     * <p>Broadcasts the given player state as the one being set.</p>
     */
    public static void broadcastPlaybackStatusChange(Context context, MusicDirectory.Entry song,
            PlayerState state) {
        Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED);
        Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED);

        switch (state) {
        case STARTED:
            intent.putExtra("state", "play");
            avrcpIntent.putExtra("playing", true);
            break;
        case STOPPED:
            intent.putExtra("state", "stop");
            avrcpIntent.putExtra("playing", false);
            break;
        case PAUSED:
            intent.putExtra("state", "pause");
            avrcpIntent.putExtra("playing", false);
            break;
        case COMPLETED:
            intent.putExtra("state", "complete");
            avrcpIntent.putExtra("playing", false);
            break;
        default:
            return; // No need to broadcast.
        }

        addTrackInfo(context, song, avrcpIntent);

        if (state != PlayerState.PREPARED) {
            //context.sendBroadcast(intent);
        }
        context.sendBroadcast(avrcpIntent);
    }

    private static void addTrackInfo(Context context, MusicDirectory.Entry song, Intent intent) {
        if (song != null) {
            DownloadService downloadService = (DownloadService) context;
            File albumArtFile = FileUtil.getAlbumArtFile(context, song);

            intent.putExtra("track", song.getTitle());
            intent.putExtra("artist", song.getArtist());
            intent.putExtra("album", song.getAlbum());
            intent.putExtra("ListSize", (long) downloadService.getSongs().size());
            intent.putExtra("id", (long) downloadService.getCurrentPlayingIndex() + 1);
            intent.putExtra("duration", (long) downloadService.getPlayerDuration());
            intent.putExtra("position", (long) downloadService.getPlayerPosition());
            intent.putExtra("coverart", albumArtFile.getAbsolutePath());

        } else {
            intent.putExtra("track", "");
            intent.putExtra("artist", "");
            intent.putExtra("album", "");
            intent.putExtra("ListSize", (long) 0);
            intent.putExtra("id", (long) 0);
            intent.putExtra("duration", (long) 0);
            intent.putExtra("position", (long) 0);
            intent.putExtra("coverart", "");
        }
    }

    /**
     * Resolves the default text color for notifications.
     *
     * Based on http://stackoverflow.com/questions/4867338/custom-notification-layouts-and-text-colors/7320604#7320604
     */
    @SuppressWarnings("deprecation")
    private static Pair<Integer, Integer> getNotificationTextColors(Context context) {
        if (NOTIFICATION_TEXT_COLORS.getFirst() == null && NOTIFICATION_TEXT_COLORS.getSecond() == null) {
            try {
                Notification notification = new Notification();
                String title = "title";
                String content = "content";
                notification.setLatestEventInfo(context, title, content, null);
                LinearLayout group = new LinearLayout(context);
                ViewGroup event = (ViewGroup) notification.contentView.apply(context, group);
                findNotificationTextColors(event, title, content);
                group.removeAllViews();
            } catch (Exception x) {
                LOG.warn("Failed to resolve notification text colors.", x);
            }
        }
        return NOTIFICATION_TEXT_COLORS;
    }

    private static void findNotificationTextColors(ViewGroup group, String title, String content) {
        for (int i = 0; i < group.getChildCount(); i++) {
            if (group.getChildAt(i) instanceof TextView) {
                TextView textView = (TextView) group.getChildAt(i);
                String text = textView.getText().toString();
                if (title.equals(text)) {
                    NOTIFICATION_TEXT_COLORS.setFirst(textView.getTextColors().getDefaultColor());
                } else if (content.equals(text)) {
                    NOTIFICATION_TEXT_COLORS.setSecond(textView.getTextColors().getDefaultColor());
                }
            } else if (group.getChildAt(i) instanceof ViewGroup)
                findNotificationTextColors((ViewGroup) group.getChildAt(i), title, content);
        }
    }

    public static void changeLanguage(Context context) {

        Resources res = context.getResources();
        DisplayMetrics dm = res.getDisplayMetrics();

        android.content.res.Configuration conf = res.getConfiguration();

        SharedPreferences prefs = getPreferences(context);
        String appLanguage = prefs.getString(Constants.PREFERENCES_KEY_LANGUAGE, "us");

        if ("auto".equalsIgnoreCase(appLanguage)) {
            conf.locale = new Locale("en");
            res.updateConfiguration(conf, dm);
        } else {
            conf.locale = new Locale(appLanguage);
            res.updateConfiguration(conf, dm);
        }
    }

    public static int getDefaultAlbums(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS, "5"));
    }

    public static int getMaxAlbums(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_MAX_ALBUMS, "20"));
    }

    public static int getDefaultSongs(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_DEFAULT_SONGS, "10"));
    }

    public static int getMaxSongs(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_MAX_SONGS, "20"));
    }

    public static int getMaxArtists(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_MAX_ARTISTS, "20"));
    }

    public static int getDefaultArtists(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS, "3"));
    }

    public static int getNetworkTimeout(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000"));
    }

    public static int getBufferLength(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_BUFFER_LENGTH, "5"));
    }

    public static boolean getGaplessPlaybackPreference(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return prefs.getBoolean(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, true);
    }

    public static boolean isNullOrWhiteSpace(String string) {
        return string == null || string.isEmpty() || string.trim().isEmpty();
    }

    public static int getDirectoryCacheTime(Context context) {
        SharedPreferences preferences = getPreferences(context);
        return Integer.parseInt(preferences.getString(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME, "300"));
    }

    public static int getChatRefreshInterval(Context context) {
        SharedPreferences prefs = getPreferences(context);
        return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL, "5000"));
    }

    public static WifiManager.WifiLock createWifiLock(Context context, String tag) {
        WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        int lockType = WifiManager.WIFI_MODE_FULL;
        if (Build.VERSION.SDK_INT >= 12) {
            lockType = 3;
        }
        return wm.createWifiLock(lockType, tag);
    }

    /**
    * This method converts dp unit to equivalent pixels, depending on device density.
    *
    * @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
    * @param context Context to get resources and device specific display metrics
    * @return A float value to represent px equivalent to dp depending on device density
    */
    public static float convertDpToPixel(float dp, Context context) {
        Resources resources = context.getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();
        return dp * (metrics.densityDpi / (float) DisplayMetrics.DENSITY_MEDIUM);
    }

    /**
     * This method converts device specific pixels to density independent pixels.
     *
     * @param px A value in px (pixels) unit. Which we need to convert into db
     * @param context Context to get resources and device specific display metrics
     * @return A float value to represent dp equivalent to px value
     */
    public static float convertPixelsToDp(float px, Context context) {
        Resources resources = context.getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();
        return px / (metrics.densityDpi / (float) DisplayMetrics.DENSITY_MEDIUM);
    }

    public static Bitmap decodeBitmap(Context context, int resourceId) {
        return BitmapFactory.decodeResource(context.getResources(), resourceId);
    }

    public static boolean isServerCompatibleTo(Context context, String version) {
        Version serverVersion = getServerRestVersion(context);
        Version requiredVersion = new Version(version);
        return serverVersion == null || serverVersion.compareTo(requiredVersion) >= 0;
    }

    public static void confirmDialog(Context context, int action, String subject,
            DialogInterface.OnClickListener onClick) {
        Util.confirmDialog(context, context.getResources().getString(action).toLowerCase(), subject, onClick);
    }

    public static void confirmDialog(Context context, String action, String subject,
            DialogInterface.OnClickListener onClick) {
        new AlertDialog.Builder(context).setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(R.string.common_confirm)
                .setMessage(context.getResources().getString(R.string.common_confirm_message, action, subject))
                .setPositiveButton(R.string.common_ok, onClick).setNegativeButton(R.string.common_cancel, null)
                .show();
    }

    public static MadsonicTheme getThemeCode(Context context, String theme, boolean fullscreen) {

        MadsonicTheme madsonicTheme = MadsonicTheme.DARK; // Fallback

        if (theme != null) {
            if (theme.startsWith("Madsonic Dark")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.DARK : MadsonicTheme.DARK_FULL;
            }
            ;
            if (theme.startsWith("Madsonic Light")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.LIGHT : MadsonicTheme.LIGHT_FULL;
            }
            ;
            if (theme.startsWith("Madsonic Holo")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.HOLO : MadsonicTheme.HOLO_FULL;
            }
            ;
            if (theme.startsWith("Madsonic Red")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.RED : MadsonicTheme.RED_FULL;
            }
            ;
            if (theme.startsWith("Madsonic Pink")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.PINK : MadsonicTheme.PINK_FULL;
            }
            ;
            if (theme.startsWith("Madsonic Flawless")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.GREEN : MadsonicTheme.GREEN_FULL;
            }
            ;
            if (theme.startsWith("Madsonic Emerald")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.EMERALD : MadsonicTheme.EMERALD_FULL;
            }
            ;
            if (theme.startsWith("Madsonic Black")) {
                madsonicTheme = !fullscreen ? MadsonicTheme.BLACK : MadsonicTheme.BLACK_FULL;
            }
            ;
        }
        return madsonicTheme;
    }

    public static int getThemeStyle(Context context, String theme, boolean fullscreen) {

        int madsonicTheme = MadsonicTheme.DARK.getThemeCode(); // Fallback

        if (theme != null) {
            if (theme.startsWith("Madsonic Dark")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Dark : R.style.Madsonic_Dark_Fullscreen;
            }
            ;
            if (theme.startsWith("Madsonic Light")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Light : R.style.Madsonic_Light_Fullscreen;
            }
            ;
            if (theme.startsWith("Madsonic Holo")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Holo : R.style.Madsonic_Holo_Fullscreen;
            }
            ;
            if (theme.startsWith("Madsonic Red")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Red : R.style.Madsonic_Red_Fullscreen;
            }
            ;
            if (theme.startsWith("Madsonic Pink")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Pink : R.style.Madsonic_Pink_Fullscreen;
            }
            ;
            if (theme.startsWith("Madsonic Flawless")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Green : R.style.Madsonic_Green_Fullscreen;
            }
            ;
            if (theme.startsWith("Madsonic Emerald")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Emerald : R.style.Madsonic_Emerald_Fullscreen;
            }
            ;
            if (theme.startsWith("Madsonic Black")) {
                madsonicTheme = !fullscreen ? R.style.Madsonic_Black : R.style.Madsonic_Black_Fullscreen;
            }
            ;
        }
        return madsonicTheme;
    }

}