jp.co.conit.sss.sp.ex1.billing.BillingService.java Source code

Java tutorial

Introduction

Here is the source code for jp.co.conit.sss.sp.ex1.billing.BillingService.java

Source

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.co.conit.sss.sp.ex1.billing;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import jp.co.conit.sss.sp.ex1.R;
import jp.co.conit.sss.sp.ex1.billing.Consts.PurchaseState;
import jp.co.conit.sss.sp.ex1.billing.Consts.ResponseCode;
import jp.co.conit.sss.sp.ex1.entity.SPResult;
import jp.co.conit.sss.sp.ex1.entity.VerifiedProduct;
import jp.co.conit.sss.sp.ex1.util.SSSApiUtil;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.android.vending.billing.IMarketBillingService;

/**
 * This class sends messages to Android Market on behalf of the application by
 * connecting (binding) to the MarketBillingService. The application creates an
 * instance of this class and invokes billing requests through this service. The
 * {@link BillingReceiver} class starts this service to process commands that it
 * receives from Android Market. You should modify and obfuscate this code
 * before using it.
 */
public class BillingService extends Service implements ServiceConnection {
    private static final String TAG = "BillingService";

    /** The service connection to the remote MarketBillingService. */
    private static IMarketBillingService mService;

    /**
     * The list of requests that are pending while we are waiting for the
     * connection to the MarketBillingService to be established.
     */
    private static LinkedList<BillingRequest> mPendingRequests = new LinkedList<BillingRequest>();

    /**
     * The list of requests that we have sent to Android Market but for which we
     * have not yet received a response code. The HashMap is indexed by the
     * request Id that each request receives when it executes.
     */
    private static HashMap<Long, BillingRequest> mSentRequests = new HashMap<Long, BillingRequest>();

    /**
     * The base class for all requests that use the MarketBillingService. Each
     * derived class overrides the run() method to call the appropriate service
     * interface. If we are already connected to the MarketBillingService, then
     * we call the run() method directly. Otherwise, we bind to the service and
     * save the request on a queue to be run later when the service is
     * connected.
     */
    abstract class BillingRequest {
        private final int mStartId;

        protected long mRequestId;

        public BillingRequest(int startId) {
            mStartId = startId;
        }

        public int getStartId() {
            return mStartId;
        }

        /**
         * Run the request, starting the connection if necessary.
         * 
         * @return true if the request was executed or queued; false if there
         *         was an error starting the connection
         */
        public boolean runRequest() {
            if (runIfConnected()) {
                return true;
            }

            if (bindToMarketBillingService()) {
                // Add a pending request to run when the service is connected.
                mPendingRequests.add(this);
                return true;
            }
            return false;
        }

        /**
         * Try running the request directly if the service is already connected.
         * 
         * @return true if the request ran successfully; false if the service is
         *         not connected or there was an error when trying to use it
         */
        public boolean runIfConnected() {
            if (Consts.DEBUG) {
                Log.d(TAG, getClass().getSimpleName());
            }
            if (mService != null) {
                try {
                    mRequestId = run();
                    if (Consts.DEBUG) {
                        Log.d(TAG, "request id: " + mRequestId);
                    }
                    if (mRequestId >= 0) {
                        mSentRequests.put(mRequestId, this);
                    }
                    return true;
                } catch (RemoteException e) {
                    onRemoteException(e);
                }
            }
            return false;
        }

        /**
         * Called when a remote exception occurs while trying to execute the
         * {@link #run()} method. The derived class can override this to execute
         * exception-handling code.
         * 
         * @param e the exception
         */
        protected void onRemoteException(RemoteException e) {
            Log.w(TAG, "remote billing service crashed");
            mService = null;
        }

        /**
         * The derived class must implement this method.
         * 
         * @throws RemoteException
         */
        abstract protected long run() throws RemoteException;

        /**
         * This is called when Android Market sends a response code for this
         * request.
         * 
         * @param responseCode the response code
         */
        protected void responseCodeReceived(ResponseCode responseCode) {
        }

        protected Bundle makeRequestBundle(String method) {
            Bundle request = new Bundle();
            request.putString(Consts.BILLING_REQUEST_METHOD, method);
            request.putInt(Consts.BILLING_REQUEST_API_VERSION, 1);
            request.putString(Consts.BILLING_REQUEST_PACKAGE_NAME, getPackageName());
            return request;
        }

        protected void logResponseCode(String method, Bundle response) {
            ResponseCode responseCode = ResponseCode
                    .valueOf(response.getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE));
            if (Consts.DEBUG) {
                Log.e(TAG, method + " received " + responseCode.toString());
            }
        }
    }

    /**
     * Wrapper class that checks if in-app billing is supported.
     */
    class CheckBillingSupported extends BillingRequest {
        public CheckBillingSupported() {
            // This object is never created as a side effect of starting this
            // service so we pass -1 as the startId to indicate that we should
            // not stop this service after executing this request.
            super(-1);
        }

        @Override
        protected long run() throws RemoteException {
            Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
            Bundle response = mService.sendBillingRequest(request);
            int responseCode = response.getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE);
            if (Consts.DEBUG) {
                Log.i(TAG, "CheckBillingSupported response code: " + ResponseCode.valueOf(responseCode));
            }
            boolean billingSupported = (responseCode == ResponseCode.RESULT_OK.ordinal());
            ResponseHandler.checkBillingSupportedResponse(billingSupported);
            return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
        }
    }

    /**
     * Wrapper class that requests a purchase.
     */
    public class RequestPurchase extends BillingRequest {
        public final String mProductId;

        public final String mDeveloperPayload;

        public RequestPurchase(String itemId) {
            this(itemId, null);
        }

        public RequestPurchase(String itemId, String developerPayload) {
            // This object is never created as a side effect of starting this
            // service so we pass -1 as the startId to indicate that we should
            // not stop this service after executing this request.
            super(-1);
            mProductId = itemId;
            mDeveloperPayload = developerPayload;
        }

        @Override
        protected long run() throws RemoteException {
            Bundle request = makeRequestBundle("REQUEST_PURCHASE");

            request.putString(Consts.BILLING_REQUEST_ITEM_ID, mProductId);

            // Note that the developer payload is optional.
            if (mDeveloperPayload != null) {
                request.putString(Consts.BILLING_REQUEST_DEVELOPER_PAYLOAD, mDeveloperPayload);
            }
            Bundle response = mService.sendBillingRequest(request);
            PendingIntent pendingIntent = response.getParcelable(Consts.BILLING_RESPONSE_PURCHASE_INTENT);
            if (pendingIntent == null) {
                return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
            }

            Intent intent = new Intent();
            ResponseHandler.buyPageIntentResponse(pendingIntent, intent);
            return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID, Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
        }

        @Override
        protected void responseCodeReceived(ResponseCode responseCode) {
            ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode);
        }
    }

    /**
     * Wrapper class that confirms a list of notifications to the server.
     */
    class ConfirmNotifications extends BillingRequest {
        final String[] mNotifyIds;

        public ConfirmNotifications(int startId, String[] notifyIds) {
            super(startId);
            mNotifyIds = notifyIds;
        }

        @Override
        protected long run() throws RemoteException {
            Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
            request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
            Bundle response = mService.sendBillingRequest(request);
            logResponseCode("confirmNotifications", response);
            return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID, Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
        }
    }

    /**
     * Wrapper class that sends a GET_PURCHASE_INFORMATION message to the
     * server.
     */
    class GetPurchaseInformation extends BillingRequest {

        final String[] mNotifyIds;

        SPResult<String> result;

        public GetPurchaseInformation(int startId, String[] notifyIds) {
            super(startId);
            mNotifyIds = notifyIds;
        }

        @Override
        protected long run() throws RemoteException {

            NonceThread nonceThread = new NonceThread();
            nonceThread.start();

            try {
                nonceThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (result == null || result.isError()) {
                throw new RemoteException();
            }
            String nonceStr = result.getContent();
            long nonce = Long.valueOf(nonceStr);

            Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
            request.putLong(Consts.BILLING_REQUEST_NONCE, nonce);
            request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
            Bundle response = mService.sendBillingRequest(request);
            logResponseCode("getPurchaseInformation", response);
            return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID, Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
        }

        @Override
        protected void onRemoteException(RemoteException e) {
            super.onRemoteException(e);
        }

        /**
         * SamuraiPurchase?nonce????
         */
        class NonceThread extends Thread {
            @Override
            public void run() {
                super.run();
                result = SSSApiUtil.getNonce(getApplicationContext());
            }
        }

    }

    /**
     * Wrapper class that sends a RESTORE_TRANSACTIONS message to the server.
     */
    public class RestoreTransactions extends BillingRequest {

        private SPResult<String> result;

        public RestoreTransactions() {
            // This object is never created as a side effect of starting this
            // service so we pass -1 as the startId to indicate that we should
            // not stop this service after executing this request.
            super(-1);
        }

        @Override
        protected long run() throws RemoteException {

            NonceThread nonceThread = new NonceThread();
            nonceThread.start();

            try {
                nonceThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (result == null || result.isError()) {
                throw new RemoteException();
            }
            String nonceStr = result.getContent();
            long nonce = Long.valueOf(nonceStr);

            Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
            request.putLong(Consts.BILLING_REQUEST_NONCE, nonce);
            Bundle response = mService.sendBillingRequest(request);
            logResponseCode("restoreTransactions", response);
            return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID, Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
        }

        @Override
        protected void onRemoteException(RemoteException e) {
            super.onRemoteException(e);
        }

        @Override
        protected void responseCodeReceived(ResponseCode responseCode) {
            ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode);
        }

        class NonceThread extends Thread {
            @Override
            public void run() {
                super.run();
                result = SSSApiUtil.getNonce(getApplicationContext());
            }
        }

    }

    public BillingService() {
        super();
    }

    public void setContext(Context context) {
        attachBaseContext(context);
    }

    /**
     * We don't support binding to this service, only starting the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onStart(Intent intent, int startId) {
        handleCommand(intent, startId);
    }

    /**
     * The {@link BillingReceiver} sends messages to this service using intents.
     * Each intent has an action and some extra arguments specific to that
     * action.
     * 
     * @param intent the intent containing one of the supported actions
     * @param startId an identifier for the invocation instance of this service
     */
    public void handleCommand(Intent intent, int startId) {

        if (intent == null) {
            return;
        }

        String action = intent.getAction();

        if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
            String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID);
            confirmNotifications(startId, notifyIds);
        } else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
            String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
            getPurchaseInformation(startId, new String[] { notifyId });
        } else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
            String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
            String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
            purchaseStateChanged(startId, signedData, signature);
        } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
            long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
            int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
                    ResponseCode.RESULT_ERROR.ordinal());
            ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
            checkResponseCode(requestId, responseCode);
        }
    }

    /**
     * Binds to the MarketBillingService and returns true if the bind succeeded.
     * 
     * @return true if the bind succeeded; false otherwise
     */
    private boolean bindToMarketBillingService() {
        try {
            if (Consts.DEBUG) {
                Log.i(TAG, "binding to Market billing service");
            }
            boolean bindResult = bindService(new Intent(Consts.MARKET_BILLING_SERVICE_ACTION), this, // ServiceConnection.
                    Context.BIND_AUTO_CREATE);

            if (bindResult) {
                return true;
            } else {
                Log.e(TAG, "Could not bind to service.");
            }
        } catch (SecurityException e) {
            Log.e(TAG, "Security exception: " + e);
        }
        return false;
    }

    /**
     * Checks if in-app billing is supported.
     * 
     * @return true if supported; false otherwise
     */
    public boolean checkBillingSupported() {
        return new CheckBillingSupported().runRequest();
    }

    /**
     * Requests that the given item be offered to the user for purchase. When
     * the purchase succeeds (or is canceled) the {@link BillingReceiver}
     * receives an intent with the action {@link Consts#ACTION_NOTIFY}. Returns
     * false if there was an error trying to connect to Android Market.
     * 
     * @param productId an identifier for the item being offered for purchase
     * @param developerPayload a payload that is associated with a given
     *            purchase, if null, no payload is sent
     * @return false if there was an error connecting to Android Market
     */
    public boolean requestPurchase(String productId, String developerPayload) {
        return new RequestPurchase(productId, developerPayload).runRequest();
    }

    /**
     * Requests transaction information for all managed items. Call this only
     * when the application is first installed or after a database wipe. Do NOT
     * call this every time the application starts up.
     * 
     * @return false if there was an error connecting to Android Market
     */
    public boolean restoreTransactions() {
        return new RestoreTransactions().runRequest();
    }

    /**
     * Confirms receipt of a purchase state change. Each {@code notifyId} is an
     * opaque identifier that came from the server. This method sends those
     * identifiers back to the MarketBillingService, which ACKs them to the
     * server. Returns false if there was an error trying to connect to the
     * MarketBillingService.
     * 
     * @param startId an identifier for the invocation instance of this service
     * @param notifyIds a list of opaque identifiers associated with purchase
     *            state changes.
     * @return false if there was an error connecting to Market
     */
    private boolean confirmNotifications(int startId, String[] notifyIds) {
        return new ConfirmNotifications(startId, notifyIds).runRequest();
    }

    /**
     * Gets the purchase information. This message includes a list of
     * notification IDs sent to us by Android Market, which we include in our
     * request. The server responds with the purchase information, encoded as a
     * JSON string, and sends that to the {@link BillingReceiver} in an intent
     * with the action {@link Consts#ACTION_PURCHASE_STATE_CHANGED}. Returns
     * false if there was an error trying to connect to the
     * MarketBillingService.
     * 
     * @param startId an identifier for the invocation instance of this service
     * @param notifyIds a list of opaque identifiers associated with purchase
     *            state changes
     * @return false if there was an error connecting to Android Market
     */
    private boolean getPurchaseInformation(int startId, String[] notifyIds) {
        return new GetPurchaseInformation(startId, notifyIds).runRequest();
    }

    /**
     * Verifies that the data was signed with the given signature, and calls
     * {@link ResponseHandler#purchaseResponse(Context, PurchaseState, String, String, long)}
     * for each verified purchase.
     * 
     * @param startId an identifier for the invocation instance of this service
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     */

    private SPResult<List<VerifiedProduct>> mVerifyPurchase;

    private void purchaseStateChanged(int startId, String signedData, String signature) {

        VrifyOrderThread vrifyOrderThread = new VrifyOrderThread(signedData, signature);
        vrifyOrderThread.start();

        try {
            vrifyOrderThread.join();
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        if (mVerifyPurchase == null || mVerifyPurchase.isError()) {
            return;
        }

        List<VerifiedProduct> purchases = mVerifyPurchase.getContent();

        if (purchases == null) {
            return;
        }

        ArrayList<String> notifyList = new ArrayList<String>();
        for (VerifiedProduct vp : purchases) {
            String notificationId = vp.getNotificationId();
            if (notificationId != null) {
                notifyList.add(notificationId);
            }
            ResponseHandler.purchaseResponse2(this, vp.getProductId(), vp.getReceipt(), vp.getPurchaseTime());
        }

        if (purchases.size() != 0) {
            if (isRestore(signedData)) {
                // 
                notificationRestore();
            } else {
                // 
                notificationVerifiedProduct(purchases);
            }
        }

        if (!notifyList.isEmpty()) {
            String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
            confirmNotifications(startId, notifyIds);
        }
    }

    /**
     * SamuraiPurchase??nonce?signature???
     */
    class VrifyOrderThread extends Thread {

        private String mSignedData;

        private String mSignature;

        private VrifyOrderThread(String signedData, String signature) {
            mSignedData = signedData;
            mSignature = signature;
        }

        @Override
        public void run() {
            super.run();
            mVerifyPurchase = Security.verifyPurchase(getApplicationContext(), mSignedData, mSignature);
        }
    }

    /**
     * ????????<br>
     * ?notificationID????????????<br>
     * 
     * @param
     * @return
     */
    private boolean isRestore(String signedData) {

        boolean isRestore = true;

        try {
            JSONObject jObject = new JSONObject(signedData);
            JSONArray jTransactionsArray = jObject.optJSONArray("orders");
            int numTransactions = 0;
            if (jTransactionsArray != null) {
                numTransactions = jTransactionsArray.length();
            }

            for (int i = 0; i < numTransactions; i++) {
                JSONObject jElement = jTransactionsArray.getJSONObject(i);
                if (jElement.has("notificationId")) {
                    isRestore = false;
                    break;
                }
            }
        } catch (JSONException e) {
            isRestore = false;
        }

        return isRestore;
    }

    /**
     * ????
     */
    private void notificationRestore() {
        Context context = getApplicationContext();
        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Notification notification = new Notification();
        Intent intent = new Intent();
        PendingIntent pendingintent = PendingIntent.getActivity(context, 0, intent, 0);

        notification.icon = R.drawable.ic_status;
        notification.tickerText = context.getString(R.string.notification_restore);
        notification.flags = Notification.FLAG_AUTO_CANCEL;
        notification.setLatestEventInfo(context, context.getString(R.string.notification_restore),
                context.getString(R.string.notification_urge_download), pendingintent);
        notificationManager.notify(1, notification);

    }

    /**
     * ????
     * 
     * @param purchases
     */
    private void notificationVerifiedProduct(List<VerifiedProduct> purchases) {

        Context context = getApplicationContext();
        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        HashMap<String, VerifiedProduct> orderMap = new HashMap<String, VerifiedProduct>();
        int size = purchases.size() - 1;

        // ????Map???????????
        for (int i = size; i >= 0; i--) {
            VerifiedProduct vp = purchases.get(i);
            if (vp.getPurchaseState() == PurchaseState.PURCHASED) {
                orderMap.put(vp.getProductId(), vp);
            }
        }

        // ?????Notification??
        // ?????
        if (orderMap.size() > 0) {

            Notification notification = new Notification();
            notification.icon = R.drawable.ic_status;
            notification.tickerText = context.getString(R.string.notification_buy_result);
            notification.flags = Notification.FLAG_AUTO_CANCEL;

            Intent intent = new Intent();
            PendingIntent pendingintent = PendingIntent.getActivity(context, 0, intent, 0);

            String title = context.getString(R.string.notification_book);

            notification.setLatestEventInfo(context, context.getString(R.string.notification_buy_finish, title),
                    context.getString(R.string.notification_urge_download), pendingintent);

            notificationManager.notify(0, notification);
        }

    }

    /**
     * This is called when we receive a response code from Android Market for a
     * request that we made. This is used for reporting various errors and for
     * acknowledging that an order was sent to the server. This is NOT used for
     * any purchase state changes. All purchase state changes are received in
     * the {@link BillingReceiver} and passed to this service, where they are
     * handled in {@link #purchaseStateChanged(int, String, String)}.
     * 
     * @param requestId a number that identifies a request, assigned at the time
     *            the request was made to Android Market
     * @param responseCode a response code from Android Market to indicate the
     *            state of the request
     */
    private void checkResponseCode(long requestId, ResponseCode responseCode) {
        BillingRequest request = mSentRequests.get(requestId);
        if (request != null) {
            if (Consts.DEBUG) {
                Log.d(TAG, request.getClass().getSimpleName() + ": " + responseCode);
            }
            request.responseCodeReceived(responseCode);
        }
        mSentRequests.remove(requestId);
    }

    /**
     * Runs any pending requests that are waiting for a connection to the
     * service to be established. This runs in the main UI thread.
     */
    private void runPendingRequests() {
        int maxStartId = -1;
        BillingRequest request;
        while ((request = mPendingRequests.peek()) != null) {
            if (request.runIfConnected()) {
                // Remove the request
                mPendingRequests.remove();

                // Remember the largest startId, which is the most recent
                // request to start this service.
                if (maxStartId < request.getStartId()) {
                    maxStartId = request.getStartId();
                }
            } else {
                // The service crashed, so restart it. Note that this leaves
                // the current request on the queue.
                bindToMarketBillingService();
                return;
            }
        }

        // If we get here then all the requests ran successfully. If maxStartId
        // is not -1, then one of the requests started the service, so we can
        // stop it now.
        if (maxStartId >= 0) {
            if (Consts.DEBUG) {
                Log.i(TAG, "stopping service, startId: " + maxStartId);
            }
            stopSelf(maxStartId);
        }
    }

    /**
     * This is called when we are connected to the MarketBillingService. This
     * runs in the main UI thread.
     */
    public void onServiceConnected(ComponentName name, IBinder service) {
        if (Consts.DEBUG) {
            Log.d(TAG, "Billing service connected");
        }
        mService = IMarketBillingService.Stub.asInterface(service);
        runPendingRequests();
    }

    /**
     * This is called when we are disconnected from the MarketBillingService.
     */
    public void onServiceDisconnected(ComponentName name) {
        Log.w(TAG, "Billing service disconnected");
        mService = null;
    }

    /**
     * Unbinds from the MarketBillingService. Call this when the application
     * terminates to avoid leaking a ServiceConnection.
     */
    public void unbind() {
        try {
            unbindService(this);
        } catch (IllegalArgumentException e) {
            // This might happen if the service was disconnected
        }
    }

}