/**
 * $URL$
 * DAAND
 * Copyright (C) 2004 - 2013, INSIDE Secure BV.  All rights reserved.
 */
package com.insidesecure.drm.android.reference;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import android.util.*;
import android.widget.Toast;
import com.insidesecure.drm.android.reference.entitlements.*;
import com.insidesecure.drm.android.reference.tools.DRMLogProvider;
import com.insidesecure.drmagent.*;
import com.insidesecure.drmagent.utils.HTTPConnectionHelperImpl;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


@TargetApi(9)
public class DRMAgentDelegate
{
    private static final String TAG = "DRMAgentDelegate";

    public static DRMAgent DRMAGENT;
    public static CertificateProvider sCertificateProvider;
    private static Lock sAgentLock = new ReentrantLock();

    private static int DRMAGENT_REFERENCE_COUNTER = 0;
    private static String sSelectedMediaPlayer;
    private static int sRuntimeActivationData;
    private static byte[] sRootCheckReport = null;
    private static SecureDeviceResult sSecureDeviceResult = null;

    public static interface DRMAgentLifecycleListener
    {
        public void destroyed();
    }

    final static List<DRMAgentLifecycleListener> sDRMAgentLifecycleListeners = new ArrayList<DRMAgentLifecycleListener>();

    public static void addDRMAgentLifecycleListener(final DRMAgentLifecycleListener drmAgentLifecycleListener)
    {
        sDRMAgentLifecycleListeners.add(drmAgentLifecycleListener);
    }

    public static void removeDRMAgentLifecycleListener(final DRMAgentLifecycleListener drmAgentLifecycleListener)
    {
        sDRMAgentLifecycleListeners.remove(drmAgentLifecycleListener);
    }

    public static boolean performRevocationListUpdate(final Context context)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);
        try
        {
            DRMAGENT.updateRevocationData(DRMScheme.PLAYREADY);
            return true;
        }
        catch (Exception e)
        {
            Log.e(TAG, "Error while retrieving revocation data update: " + e.getMessage(), e);
            return false;
        }
    }

    public static void deleteDRMDatabases(final Context context)
    {
        sAgentLock.lock();
        try
        {
            int previousReferenceCount = DRMAGENT_REFERENCE_COUNTER;

            // Force a release
            DRMAGENT_REFERENCE_COUNTER = 1;

            uninitialize();

            Log.d(TAG, "Deleting DRM databases");


            File f = new File(context.getFilesDir().getParent() + File.separator + "playready.hds");
            if (!f.delete())
            {
                Log.w(TAG, "Could not delete playready.hds");
            }

            if (previousReferenceCount > 0)
            {
                initialize(context);
            }

            DRMAGENT_REFERENCE_COUNTER = previousReferenceCount;

        }
        finally
        {
            sAgentLock.unlock();
        }
    }

    public static void reconfigure(Context context)
    {
        try
        {
            if (!sAgentLock.tryLock(5000, TimeUnit.MILLISECONDS))
            {
                Log.e(TAG, "Timeout received while waiting for lock");
                throw new RuntimeException("Timeout received while waiting for lock");
            }
            try
            {
                if (DRMAGENT != null)
                {
                    Log.d(TAG, "Agent initialized, will reconfigure");
                    setupDRMAgentConfiguration(DRMAGENT, context);
                }
            }
            finally
            {
                sAgentLock.unlock();
            }
        }
        catch (Exception e)
        {
            Log.e(TAG, "Error while reconfiguring: " + e.getMessage(), e);
        }
    }

    public static void setSelectedMediaPlayer(final String selectedMediaPlayer)
    {
        sSelectedMediaPlayer = selectedMediaPlayer;
    }

    public static void setRuntimeActivationData(int runtimeActivationData)
    {
        sRuntimeActivationData = runtimeActivationData;
    }

    public interface CertificateProvider
    {
        public int getPRPrivateKey();

        public int getPRCertificate();
    }

    public static DRMAgent initialize(final Context context)
    {
        Tools.DEFENSE("context", context);
        try
        {
            if (!sAgentLock.tryLock(5000, TimeUnit.MILLISECONDS))
            {
                Log.e(TAG, "Timeout received while waiting for lock");
                throw new RuntimeException("Timeout received while waiting for lock");
            }

            try
            {
                Log.d(TAG, "Initializing with " + DRMAGENT_REFERENCE_COUNTER + " DRM agent instances available");

                if (DRMAGENT != null)
                {
                    Log.d(TAG, "Already initialized, will not do that again");

                    setupDRMAgentConfiguration(DRMAGENT, context);

                    DRMAGENT_REFERENCE_COUNTER++;

                    return DRMAGENT;
                }

                if (DRMAgent.DRMAgentFactory.isInitialized())
                {
                    Log.w(TAG, "Agent instance already initialized, but we have no reference to it");
                }

                final List<Pair<PKIType, InputStream>> pkiInformation = new ArrayList<Pair<PKIType, InputStream>>();
                if (sCertificateProvider != null)
                {
                    // Load the certificates now
                    try
                    {
                        Log.i(TAG, "Loading certificate and key: " + sCertificateProvider);
                        pkiInformation.add(new Pair<PKIType, InputStream>(PKIType.PLAYREADY_MODEL_PRIVATE_KEY, resolveResource(context, PKIType.PLAYREADY_MODEL_PRIVATE_KEY)));
                        pkiInformation.add(new Pair<PKIType, InputStream>(PKIType.PLAYREADY_MODEL_CERTIFICATE, resolveResource(context, PKIType.PLAYREADY_MODEL_CERTIFICATE)));
                        Log.i(TAG, "Certificate and key imported");
                    }
                    catch(RuntimeException rte)
                    {
                        throw rte;
                    }
                    catch (Exception e)
                    {
                        Log.w(TAG, "Could not load certificate and key: " + e.getMessage(), e);
                    }
                }

                Log.d(TAG, "Initializing Agent instance");

                // Setup and instantiate the DRM agent using the factory provided

                final SharedPreferences insideSecureSharedPref = context.getSharedPreferences(Constants.PREFERENCES_FILENAME, Context.MODE_PRIVATE);
                int logLevel = insideSecureSharedPref.getInt(Constants.PREFERENCES_AGENT_LOG_LEVEL, 4);

                // Ensure the value is within bounds
                if (logLevel < 0)
                {
                    logLevel = 0;
                }
                else if (logLevel >= DRMLogLevel.values().length)
                {
                    logLevel = DRMLogLevel.values().length - 1;
                }

                Log.d(TAG, "Initializing DRM Agent with log level: " + logLevel + " : (" + DRMLogLevel.values()[logLevel] + ")");

                // Initialize using the Inside Secure Runtime Activation Data
                byte[] runtimeActivationData = resolveRuntimeApplicationData(context);

                Log.d(TAG, "Using " + runtimeActivationData.length + " byte(s) of runtime activation data");

                final DRMAgent.DRMAgentFactory.DRMAgentInstanceCreationRequest drmAgentInstanceCreationRequest = new DRMAgent.DRMAgentFactory.DRMAgentInstanceCreationRequest(context, runtimeActivationData, pkiInformation, sSelectedMediaPlayer);
                drmAgentInstanceCreationRequest.setDRMLogLevel(DRMLogLevel.values()[logLevel]);

                // For the configuration to be set on the instance creation request
                // we need to create our own instance - in most other cases, you would
                // retrieve it from DRM agent first
                DRMAgentConfiguration drmAgentConfiguration = new DRMAgentConfiguration();
                populateDRMAgentConfiguration(context, drmAgentConfiguration);
                drmAgentInstanceCreationRequest.setDRMAgentConfiguration(drmAgentConfiguration);

                // Create the DRM agent instance
                DRMAGENT = DRMAgent.DRMAgentFactory.getInstance(drmAgentInstanceCreationRequest);
                DRMAGENT.setEntitlementDelegate(new CallbackEnabledSamplePlayReadyEntitlementDelegate(context, new CallbackEnabledSamplePlayReadyEntitlementDelegate.Callback()
                {
                    @Override
                    public void actionInitiated(final Action action, final DRMContent drmContent)
                    {
                        Log.d(TAG,"Action " + action + " initiated");
                    }

                    @Override
                    public void actionCompleted(final Action action, final DRMContent drmContent, final DRMError result)
                    {
                        Log.d(TAG,"Action " + action + " completed with result: " + result);
                    }
                }, Looper.getMainLooper()));

                try
                {
                    performSecureDeviceCheck(DRMAGENT, context);
                }
                catch (Exception exception)
                {
                    Log.e(TAG, "Error while getting device state: " + exception.getMessage(), exception);
                }

                // Add the DRM callback listener
                DRMAGENT.addDRMCallbackListener(new AbstractDRMCallbackListener()
                {
                    public String mediaDescriptorReceived(String descriptor, final URI uri)
                    {
                        Log.d(TAG, "Media descriptor received: " + uri);
                        return descriptor;
                    }

                    public void silentEntitlementAcquisitionInitiated(final AcquireLicenseRequest acquireLicenseRequest)
                    {
                        Log.d(TAG, "Silent acquisition initiated");
                    }
                });

                DRMAgent.DRMAgentFactory.setDRMLogCallback(insideSecureSharedPref.getBoolean(Constants.PREFERENCES_STORE_DRM_LOGS, false) ? DRMLogProvider.getInstance() : null);

                drmAgentConfiguration = DRMAGENT.getDRMAgentConfiguration();
                DRMAGENT.setDRMAgentConfiguration(drmAgentConfiguration);

                DRMAGENT_REFERENCE_COUNTER++;

                return DRMAGENT;
            }
            finally
            {
                sAgentLock.unlock();
            }

        }
        catch (Exception e)
        {
            Log.e(TAG, "Error while initializing: " + e.getMessage(), e);
            throw new RuntimeException("Error while initializing: " + e.getMessage(), e);
        }
    }

    private static InputStream resolveResource(Context context, final PKIType pkiType) throws FileNotFoundException
    {
        final String path;
        final int rawResourceID;
        switch (pkiType)
        {
            case PLAYREADY_MODEL_PRIVATE_KEY:
                path = "/sdcard/insidesecure/pr_privatekey";
                rawResourceID = sCertificateProvider.getPRPrivateKey();
                break;
            case PLAYREADY_MODEL_CERTIFICATE:
                path = "/sdcard/insidesecure/pr_certificate";
                rawResourceID = sCertificateProvider.getPRCertificate();
                break;
            default:
                throw new IllegalArgumentException("Unhandled PKI type: " + pkiType);
        }

        //first try to load from filesystem
        InputStream inputStream = openFileInputStream(path);
        if (inputStream != null)
        {
            Log.d(TAG, "Loaded resource of type " + pkiType + " from filesystem path " + path);
            return inputStream;
        }
        //try now to load from resources
        if(rawResourceID==0)
        {
            String errMsg = "Needed resource " + pkiType + " not found during initialization. Please consult documentation on how to provide needed resources.";
            Log.e(TAG, errMsg);
            throw new RuntimeException(errMsg);
        }
        else
        {
            return context.getResources().openRawResource(rawResourceID);
        }
    }

    private static InputStream openFileInputStream(final String path) throws FileNotFoundException
    {
        File file = new File(path);
        if (file.exists() && file.canRead())
        {
            return new FileInputStream(file);
        }
        return null;
    }

    private static void setupDRMAgentConfiguration(final DRMAgent drmAgentInstance, final Context context)
    {
        // Always update any of the preferences that are used dynamically
        final DRMAgentConfiguration drmAgentConfiguration = drmAgentInstance.getDRMAgentConfiguration();
        populateDRMAgentConfiguration(context, drmAgentConfiguration);
        drmAgentInstance.setDRMAgentConfiguration(drmAgentConfiguration);
    }

    private static void populateDRMAgentConfiguration(final Context context, final DRMAgentConfiguration drmAgentConfiguration)
    {
        // Setup the agent to use a cache outside of the application folder
        // This cache is used when doing Download & Play in the application
        drmAgentConfiguration.setContentCachePath("/sdcard/insidesecure/content-cache");

        final SharedPreferences insideSecureSharedPref = context.getSharedPreferences(Constants.PREFERENCES_FILENAME, Context.MODE_PRIVATE);

        drmAgentConfiguration.setUseHeadlessHLS(insideSecureSharedPref.getBoolean(Constants.PREFERENCES_USE_HEADLESS_HLS, false));
        drmAgentConfiguration.setHttpsTrustAllSSLCertificates(insideSecureSharedPref.getBoolean(Constants.PREFERENCES_HTTPS_CLIENT_TRUST_ALL_CERTS, false));

        /** Sets up a sample HttpConnectionHelper that will be invoked for operations over the network.  With the helper it is posisble
         * to do the following:
         *
         * - Perform general setup of the used URLConnection
         * - Add request headers to the outgoing requests
         * - Rewrite outgoing URLs before they are used
         *
         * See HttpConnectionHelper JavaDoc for more information.
         */
        drmAgentConfiguration.setHttpConnectionHelper(new HTTPConnectionHelperImpl()
        {
            @Override
            public void addHeaders(final RequestType requestType, final URL url, final Map<String, List<String>> headers)
            {
                headers.put("INSIDESecure", Arrays.asList("1.0"));
                super.addHeaders(requestType, url, headers);
            }
        });

        /** Install a new, global cookie handler.  This will be used by all Agent HTTP requests and allow our client to add custom cookies
         * to the outbound requests.
         */
        CookieHandler.setDefault(new CookieManager()
        {
            @Override
            public Map<String, List<String>> get(final URI uri, final Map<String, List<String>> requestHeaders) throws IOException
            {
                final Map<String, List<String>> cookiesFromCookieManager = super.get(uri, requestHeaders);
                final Map<String, List<String>> cookies = new HashMap<String, List<String>>(cookiesFromCookieManager);
                cookies.put("INSIDE", Arrays.asList("I love cookies"));
                return cookies;
            }

            @Override
            public void put(final URI uri, final Map<String, List<String>> responseHeaders) throws IOException
            {
                // Clients can implement this method to store returned values from the server
                super.put(uri, responseHeaders);
            }
        });

        // Update the configuration to be used
        drmAgentConfiguration.setUserAgent(Constants.CONFIGURATION_DRM_AGENT_USER_STRING);

        // Load any additional properties from file (if available)
        loadDRMAgentConfigurationProperties(drmAgentConfiguration);

    }

    private static byte[] loadRuntimeActivationData(InputStream binaryData) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Tools.copyStream(binaryData, outputStream);

        binaryData.close();
        outputStream.flush();
        outputStream.close();

        return outputStream.toByteArray();
    }

    private static byte[] resolveRuntimeApplicationData(Context context)
    {
        File f = new File(Constants.CONFIGURATION_CDESC_PATH + Constants.CONFIGURATION_RUNTIME_APPLICATION_DATA_FILE);

        if (f.exists())
        {
            Log.d(TAG, "Found available runtime activation data in " + Constants.CONFIGURATION_CDESC_PATH + ", will use that");

            try
            {
                return loadRuntimeActivationData(new FileInputStream(f));
            }
            catch (IOException e)
            {
                Log.e(TAG, "Error while loading rad: " + e.getMessage(), e);
                throw new RuntimeException("Error while loading rad: " + e.getMessage(), e);
            }
        }
        else
        {
            if(sRuntimeActivationData==0)
            {
                final String msg = "Could not resolve any valid runtime activation data.  Please consult documentation on how to include the required information with the app.";
                Log.e(TAG, msg);
                throw new RuntimeException(msg);
            }
            else
            {
                try
                {
                    return loadRuntimeActivationData(context.getResources().openRawResource(sRuntimeActivationData));
                }
                catch (IOException e)
                {
                    Log.e(TAG, "Error while loading rad: " + e.getMessage(), e);
                    throw new RuntimeException("Error while loading rad: " + e.getMessage(), e);
                }
            }
        }
    }


    private static void loadDRMAgentConfigurationProperties(final DRMAgentConfiguration drmAgentConfiguration)
    {
        File f = new File(Constants.CONFIGURATION_CDESC_PATH + Constants.CONFIGURATION_PROPERTIES_FILE);

        if (!f.exists())
        {
            Log.d(TAG,"No drmagent-configuration.properties found, will bail");
            return;
        }

        try
        {
            Properties properties = new Properties();
            BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(f));
            try
            {
                properties.load(inStream);
            }
            finally
            {
                inStream.close();
            }

            final Set<String> keys = properties.stringPropertyNames();

            Log.v(TAG,"Loaded configuration properties: " + keys.size() + " properties");

            for (final String key : keys)
            {
                final int firstDotIndex = key.indexOf(".");

                // If it doesn't contain a dot we are not treating it specially
                if (firstDotIndex == -1)
                {
                    continue;
                }

                final String type = key.substring(0, firstDotIndex);
                final String configKey = key.substring(firstDotIndex+1);
                final String value = properties.getProperty(key);

                Log.v(TAG,"   " + configKey + "/" + value + "/" + type);

                if ("float".equals(type))
                {
                    Float fValue = Float.parseFloat(value);
                    drmAgentConfiguration.getDeviceProperties().put(configKey,fValue);

                }
                else if ("long".equals(type))
                {
                    Long lValue = Long.parseLong(value);
                    drmAgentConfiguration.getDeviceProperties().put(configKey,lValue);
                }
                else if ("int".equals(type))
                {
                    Integer iValue = Integer.parseInt(value);
                    drmAgentConfiguration.getDeviceProperties().put(configKey,iValue);
                }
                else if ("boolean".equals(type))
                {
                    Boolean bValue = Boolean.parseBoolean(value);
                    drmAgentConfiguration.getDeviceProperties().put(configKey,bValue);
                }
                else if ("string".equals(type))
                {
                    drmAgentConfiguration.getDeviceProperties().put(configKey,value);
                }
                else if ("ref-app".equals(type))
                {
                    // FIXME: HACKED OUT CODE
/*
                    // Special reference application setting
                    if ("drm.license.acquisition.handler.type".equals(configKey))
                    {
                        try
                        {
                            Log.d(TAG,"Loading custom license acquisition handler: " + value);
                            // Special license acquisition handler is in use
                            final Class clazz = Class.forName(value);
                            Constructor constructor = null;
                            DRMLicenseAcquisitionHandler drmLicenseAcquisitionHandler;
                            try
                            {
                                constructor = clazz.getDeclaredConstructor(Properties.class);
                                drmLicenseAcquisitionHandler = (DRMLicenseAcquisitionHandler) constructor.newInstance(properties);
                            }
                            catch (NoSuchMethodException e)
                            {
                                // Use empty constructor
                                constructor = clazz.getDeclaredConstructor();
                                drmLicenseAcquisitionHandler = (DRMLicenseAcquisitionHandler) constructor.newInstance();
                            }
                            drmAgentConfiguration.setDRMLicenseAcquisitionHandler(drmLicenseAcquisitionHandler);
                        }
                        catch (Exception e)
                        {
                            Log.e(TAG,"Error while loading custom license acquisition handler: " + e.getMessage(),e);
                        }
                    }
*/
                }
                else
                {
                    Log.e(TAG,"Unhandled type: " + type);
                }

            }


        }
        catch (Exception e)
        {
            Log.e(TAG,"Error while loading properties: " + e.getMessage(), e);
            throw new RuntimeException("Error while loading properties: " + e.getMessage(),e);
        }

    }

    public static void setOfflineMode(final boolean offlineMode)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);

        final DRMAgentConfiguration drmAgentConfiguration = DRMAGENT.getDRMAgentConfiguration();
        // if Offline mode is enabled DRMAgent will not touch the network even if it is available
        drmAgentConfiguration.setOfflineMode(offlineMode);
        DRMAgentDelegate.DRMAGENT.setDRMAgentConfiguration(drmAgentConfiguration);
    }

    public static String getDRMInformation(Context context)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);

        final DRMAgentVersionInfo drmVersion = getDRMAgentVersionInfo();

        if (drmVersion != null)
        {
            return String.format("%s\t%s\n%s\t%s\n%s\t%s\n%s\t%s",
                    "DRM Agent API Version:", drmVersion.getAPIVersion(),
                    "DRM Agent API Build Date:", drmVersion.getAPIBuildDate(),
                    "PlayReady Identifier:", getDRMAgentInstance(context).getPlayReadyDeviceID().toString(),
                    "Device State: ", "Retrieving ..."
            ) + DRMFusionAgentReferenceApplication.sMiscVersionInfo;
        }
        return null;
    }

    public static DRMAgentVersionInfo getDRMAgentVersionInfo()
    {
        return DRMAgent.DRMAgentFactory.getDRMVersion();
    }

    public static void purgeDatabase(final Context context)
    {
        sAgentLock.lock();
        try
        {
            int previousReferenceCount = DRMAGENT_REFERENCE_COUNTER;

            // Force a release
            DRMAGENT_REFERENCE_COUNTER = 1;

            uninitialize();

            Log.d(TAG, "Purging DRM databases");

            DRMAgent.DRMAgentFactory.purgeDatabase(context);

            if (previousReferenceCount > 0)
            {
                initialize(context);
            }

            DRMAGENT_REFERENCE_COUNTER = previousReferenceCount;
        }
        finally
        {
            sAgentLock.unlock();
        }
    }

    public static void purgeCachedFiles(final Context context)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);
        DRMAGENT.cleanup(DRMPurgeOption.CACHED_MEDIA);
        ContentHandler.clearCache();
    }

    public static void purgePooledObjects(final Context context)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);
        DRMAGENT.cleanup(DRMPurgeOption.POOLED_OBJECT_INSTANCES);
    }


    public static void purgeLicenses(final Context context)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);
        // Time to purge the database
        DRMAGENT.cleanup(DRMPurgeOption.LICENSES);
    }

// FIXME: HACKED OUT CODE
/*
    public static boolean activateDRMContent(final Context context, final DRMContent drmContent, String customData, final DRMLicenseAcquisitionHandler drmLicenseAcquisitionHandler)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);
        try
        {
            Tools.DEFENSE("drmContent", drmContent);

            final AcquireLicenseRequest acquireLicenseRequest = new AcquireLicenseRequest(drmContent, drmLicenseAcquisitionHandler);

            if (customData != null)
            {
                Log.d(TAG, "Setting License acquisition request custom data to: " + customData);
                acquireLicenseRequest.setCustomData(customData);
            }
            else
            {
                Log.d(TAG, "Custom data not provided, License acquisition request custom data will be: " + acquireLicenseRequest.getCustomData());
            }

            return DRMAGENT.acquireLicense(acquireLicenseRequest);
        }
        catch (DRMAgentException e)
        {
            Log.e(TAG, e.getMessage(), e);
            return false;
        }
    }
*/


    public static DRMContent getDRMContent(final Context context1, final URI uri, DRMContentFormat drmContentFormat, DRMScheme drmScheme)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);


        return DRMAGENT.getDRMContent(uri, drmContentFormat, drmScheme, new MediaManifest.MediaManifestUpdateListener()
        {
            @Override
            public void onMediaManifestUpdate(final DRMContent drmContent, final MediaManifest mediaManifest)
            {
                Log.d(TAG,"Media manifest updated");
            }
        });
    }

    public static DRMAgent getDRMAgentInstance(final Context context)
    {
        Tools.DEFENSE("DRMAGENT", DRMAGENT);
        return DRMAGENT;
    }

    public static void setDRMContentListener(final DRMContent drmContent, final DRMContentListener drmContentListener)
    {
        Tools.DEFENSE("drmContent", drmContent);
        drmContent.setDRMContentListener(drmContentListener);
    }

    public static boolean dumpRootCheckReport(String fileName)
    {

        if (sRootCheckReport == null)
        {
            Log.d(TAG, "No root check report available");
            return false;
        }

        Tools.DEFENSE("DRMAGENT", DRMAGENT);

        try
        {
            FileOutputStream fileOutputStream = new FileOutputStream(fileName);
            fileOutputStream.write(sRootCheckReport);
            fileOutputStream.flush();
            fileOutputStream.close();
        }

        catch (Exception exception)
        {
            Log.e(TAG, "Unexpected error: " + exception.getMessage());
            return false;
        }

        return true;
    }

    public static void uninitialize()
    {
        try
        {
            if (!sAgentLock.tryLock(5000, TimeUnit.MILLISECONDS))
            {
                Log.e(TAG,"Timeout received while waiting for lock");
                throw new RuntimeException("Timeout received while waiting for lock");
            }
        }
        catch (InterruptedException e)
        {
            Log.e(TAG,"Interrupted while waiting for lock");
            throw new RuntimeException("Interrupted while waiting for lock");
        }

        try
        {
            if (DRMAGENT != null)
            {
                Log.d(TAG,"DRM agent instance references: " + DRMAGENT_REFERENCE_COUNTER);
                if (DRMAGENT_REFERENCE_COUNTER == 1)
                {
                    Log.d(TAG,"Last referenced DRM agent instance reached, will release it");
                    DRMAgent.DRMAgentFactory.releaseInstance();
                    DRMAGENT = null;

                    for (DRMAgentLifecycleListener drmAgentLifecycleListener : sDRMAgentLifecycleListeners)
                    {
                        drmAgentLifecycleListener.destroyed();
                    }
                }
                else
                {
                    Log.d(TAG,"Still " + (DRMAGENT_REFERENCE_COUNTER-1) + " references to DRM agent instance out there, will not release");
                }
                DRMAGENT_REFERENCE_COUNTER--;
            }
        }
        finally
        {
            sAgentLock.unlock();
        }
    }

    private static void performSecureDeviceCheck(final DRMAgent drmAgent, final Context context)
    {

        Log.d(TAG, "Will retrieve device state...");

        sSecureDeviceResult = null;

        try
        {
            drmAgent.performSecureDeviceCheck(context, new SecureDeviceCheckCallback()
            {


                @Override
                public void onCompleted(SecureDeviceResult secureDeviceResult)
                {
                    boolean isSecureDevice = secureDeviceResult.isSecureDevice();

                    Log.d(TAG, "Device State: " + (isSecureDevice ? "Secure" : "Insecure"));

                    Toast.makeText(context, "Device Status: " + (isSecureDevice ? "Secure" : "Not secure"), Toast.LENGTH_LONG).show();

                    sSecureDeviceResult = secureDeviceResult;
                    sRootCheckReport = secureDeviceResult.mRootCheckReport;
                }
            });
        }

        catch (Exception exception)
        {
            Log.e(TAG, "Error while retrieving device state: " + exception.getMessage(), exception);
        }
    }

    public static SecureDeviceResult getSecureDeviceResult()
    {

        return sSecureDeviceResult;
    }
}
