package com.android.bluetooth.sap;

import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSap;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothSap;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.Log;

import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.sdp.SdpManager;
import com.android.internal.annotations.VisibleForTesting;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@TargetApi(Build.VERSION_CODES.ECLAIR)
public class SapService extends ProfileService {

    private static final String SDP_SAP_SERVICE_NAME = "SIM Access";
    private static final int SDP_SAP_VERSION = 0x0102;
    private static final String TAG = "SapService";
    public static final boolean DEBUG = false;
    public static final boolean VERBOSE = false;

    /* Message ID's */
    private static final int START_LISTENER = 1;
    private static final int USER_TIMEOUT = 2;
    private static final int SHUTDOWN = 3;

    public static final int MSG_SERVERSESSION_CLOSE = 5000;
    public static final int MSG_SESSION_ESTABLISHED = 5001;
    public static final int MSG_SESSION_DISCONNECTED = 5002;

    public static final int MSG_ACQUIRE_WAKE_LOCK = 5005;
    public static final int MSG_RELEASE_WAKE_LOCK = 5006;

    public static final int MSG_CHANGE_STATE = 5007;

    /* Each time a transaction between the SIM and the BT Client is detected a wakelock is taken.
     * After an idle period of RELEASE_WAKE_LOCK_DELAY ms the wakelock is released.
     *
     * NOTE: While connected the the Nokia 616 car-kit it was noticed that the carkit do
     *       TRANSFER_APDU_REQ with 20-30 seconds interval, and it sends no requests less than 1 sec
     *       apart. Additionally the responses from the RIL seems to come within 100 ms, hence a
     *       one second timeout should be enough.
     */
    private static final int RELEASE_WAKE_LOCK_DELAY = 1000;

    /* Intent indicating timeout for user confirmation. */
    public static final String USER_CONFIRM_TIMEOUT_ACTION =
            "com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT";
    private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;

    private PowerManager.WakeLock mWakeLock = null;
    private BluetoothAdapter mAdapter;
    private SocketAcceptThread mAcceptThread = null;
    private BluetoothServerSocket mServerSocket = null;
    private int mSdpHandle = -1;
    private BluetoothSocket mConnSocket = null;
    private BluetoothDevice mRemoteDevice = null;
    private static String sRemoteDeviceName = null;
    private volatile boolean mInterrupted;
    private int mState;
    private SapServer mSapServer = null;
    private AlarmManager mAlarmManager = null;
    private boolean mRemoveTimeoutMsg = false;

    private boolean mIsWaitingAuthorization = false;
    private boolean mIsRegistered = false;

    private static SapService sSapService;

    private static final ParcelUuid[] SAP_UUIDS = {
            BluetoothUuid.SAP,
    };


    public SapService() {
        mState = BluetoothSap.STATE_DISCONNECTED;
    }

    /***
     * Call this when ever an activity is detected to renew the wakelock
     *
     * @param messageHandler reference to the handler to notify
     *  - typically mSessionStatusHandler, but it cannot be accessed in a static manner.
     */
    public static void notifyUpdateWakeLock(Handler messageHandler) {
        if (messageHandler != null) {
            Message msg = Message.obtain(messageHandler);
            msg.what = MSG_ACQUIRE_WAKE_LOCK;
            msg.sendToTarget();
        }
    }

    private void removeSdpRecord() {
        if (mAdapter != null && mSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
            if (VERBOSE) {
                Log.d(TAG, "Removing SDP record handle: " + mSdpHandle);
            }
            boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
            mSdpHandle = -1;
        }
    }

    private void startRfcommSocketListener() {
        if (VERBOSE) {
            Log.v(TAG, "Sap Service startRfcommSocketListener");
        }

        if (mAcceptThread == null) {
            mAcceptThread = new SocketAcceptThread();
            mAcceptThread.setName("SapAcceptThread");
            mAcceptThread.start();
        }
    }

    private static final int CREATE_RETRY_TIME = 10;

    private boolean initSocket() {
        if (VERBOSE) {
            Log.v(TAG, "Sap Service initSocket");
        }

        boolean initSocketOK = false;

        // It's possible that create will fail in some cases. retry for 10 times
        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
            initSocketOK = true;
            try {
                // It is mandatory for MSE to support initiation of bonding and encryption.
                // TODO: Consider reusing the mServerSocket - it is indented to be reused
                //       for multiple connections.
                mServerSocket = mAdapter.listenUsingRfcommOn(
                        BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true, true);
                removeSdpRecord();
                mSdpHandle = SdpManager.getDefaultManager()
                        .createSapsRecord(SDP_SAP_SERVICE_NAME, mServerSocket.getChannel(),
                                SDP_SAP_VERSION);
            } catch (IOException e) {
                Log.e(TAG, "Error create RfcommServerSocket ", e);
                initSocketOK = false;
            }

            if (!initSocketOK) {
                // Need to break out of this loop if BT is being turned off.
                if (mAdapter == null) {
                    break;
                }
                int state = mAdapter.getState();
                if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state
                        != BluetoothAdapter.STATE_ON)) {
                    Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
                    break;
                }
                try {
                    if (VERBOSE) {
                        Log.v(TAG, "wait 300 ms");
                    }
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    Log.e(TAG, "socketAcceptThread thread was interrupted (3)", e);
                }
            } else {
                break;
            }
        }

        if (initSocketOK) {
            if (VERBOSE) {
                Log.v(TAG, "Succeed to create listening socket ");
            }

        } else {
            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
        }
        return initSocketOK;
    }

    private synchronized void closeServerSocket() {
        // exit SocketAcceptThread early
        if (mServerSocket != null) {
            try {
                // this will cause mServerSocket.accept() return early with IOException
                mServerSocket.close();
                mServerSocket = null;
            } catch (IOException ex) {
                Log.e(TAG, "Close Server Socket error: ", ex);
            }
        }
    }

    private synchronized void closeConnectionSocket() {
        if (mConnSocket != null) {
            try {
                mConnSocket.close();
                mConnSocket = null;
            } catch (IOException e) {
                Log.e(TAG, "Close Connection Socket error: ", e);
            }
        }
    }

    private void closeService() {
        if (VERBOSE) {
            Log.v(TAG, "SAP Service closeService in");
        }

        // exit initSocket early
        mInterrupted = true;
        closeServerSocket();

        if (mAcceptThread != null) {
            try {
                mAcceptThread.shutdown();
                mAcceptThread.join();
                mAcceptThread = null;
            } catch (InterruptedException ex) {
                Log.w(TAG, "mAcceptThread close error", ex);
            }
        }

        if (mWakeLock != null) {
            mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
            mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
            mWakeLock.release();
            mWakeLock = null;
        }

        closeConnectionSocket();

        if (VERBOSE) {
            Log.v(TAG, "SAP Service closeService out");
        }
    }

    private void startSapServerSession() throws IOException {
        if (VERBOSE) {
            Log.v(TAG, "Sap Service startSapServerSession");
        }

        // acquire the wakeLock before start SAP transaction thread
        if (mWakeLock == null) {
            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StartingSapTransaction");
            mWakeLock.setReferenceCounted(false);
            mWakeLock.acquire();
        }

        /* Start the SAP I/O thread and associate with message handler */
        mSapServer = new SapServer(mSessionStatusHandler, this, mConnSocket.getInputStream(),
                mConnSocket.getOutputStream());
        mSapServer.start();
        /* Warning: at this point we most likely have already handled the initial connect
         *          request from the SAP client, hence we need to be prepared to handle the
         *          response. (the SapHandler should have been started before this point)*/

        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
        mSessionStatusHandler.sendMessageDelayed(
                mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
                RELEASE_WAKE_LOCK_DELAY);

        if (VERBOSE) {
            Log.v(TAG, "startSapServerSession() success!");
        }
    }

    private void stopSapServerSession() {

        /* When we reach this point, the SapServer is closed down, and the client is
         * supposed to close the RFCOMM connection. */
        if (VERBOSE) {
            Log.v(TAG, "SAP Service stopSapServerSession");
        }

        mAcceptThread = null;
        closeConnectionSocket();
        closeServerSocket();

        setState(BluetoothSap.STATE_DISCONNECTED);

        if (mWakeLock != null) {
            mWakeLock.release();
            mWakeLock = null;
        }

        // Last SAP transaction is finished, we start to listen for incoming
        // rfcomm connection again
        if (mAdapter.isEnabled()) {
            startRfcommSocketListener();
        }
    }

    /**
     * A thread that runs in the background waiting for remote rfcomm
     * connect.Once a remote socket connected, this thread shall be
     * shutdown.When the remote disconnect,this thread shall run again waiting
     * for next request.
     */
    private class SocketAcceptThread extends Thread {

        private boolean mStopped = false;

        @Override
        public void run() {
            BluetoothServerSocket serverSocket;
            if (mServerSocket == null) {
                if (!initSocket()) {
                    return;
                }
            }

            while (!mStopped) {
                try {
                    if (VERBOSE) {
                        Log.v(TAG, "Accepting socket connection...");
                    }
                    serverSocket = mServerSocket;
                    if (serverSocket == null) {
                        Log.w(TAG, "mServerSocket is null");
                        break;
                    }
                    mConnSocket = mServerSocket.accept();
                    if (VERBOSE) {
                        Log.v(TAG, "Accepted socket connection...");
                    }
                    synchronized (SapService.this) {
                        if (mConnSocket == null) {
                            Log.w(TAG, "mConnSocket is null");
                            break;
                        }
                        mRemoteDevice = mConnSocket.getRemoteDevice();
                    }
                    if (mRemoteDevice == null) {
                        Log.i(TAG, "getRemoteDevice() = null");
                        break;
                    }

                    sRemoteDeviceName = mRemoteDevice.getName();
                    // In case getRemoteName failed and return null
                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
                        sRemoteDeviceName = getString(R.string.defaultname);
                    }
                    int permission = mRemoteDevice.getSimAccessPermission();

                    if (VERBOSE) {
                        Log.v(TAG, "getSimAccessPermission() = " + permission);
                    }

                    if (permission == BluetoothDevice.ACCESS_ALLOWED) {
                        try {
                            if (VERBOSE) {
                                Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
                                        + " automatically as trusted device");
                            }
                            startSapServerSession();
                        } catch (IOException ex) {
                            Log.e(TAG, "catch exception starting obex server session", ex);
                        }
                    } else if (permission != BluetoothDevice.ACCESS_REJECTED) {
                        Intent intent =
                                new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
                        intent.setPackage(getString(R.string.pairing_ui_package));
                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                                BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
                        intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());

                        mIsWaitingAuthorization = true;
                        setUserTimeoutAlarm();
                        sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);

                        if (VERBOSE) {
                            Log.v(TAG, "waiting for authorization for connection from: "
                                    + sRemoteDeviceName);
                        }

                    } else {
                        // Close RFCOMM socket for current connection and start listening
                        // again for new connections.
                        Log.w(TAG, "Can't connect with " + sRemoteDeviceName
                                + " as access is rejected");
                        if (mSessionStatusHandler != null) {
                            mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
                        }
                    }
                    mStopped = true; // job done ,close this thread;
                } catch (IOException ex) {
                    mStopped = true;
                    if (VERBOSE) {
                        Log.v(TAG, "Accept exception: ", ex);
                    }
                }
            }
        }

        void shutdown() {
            mStopped = true;
            interrupt();
        }
    }

    private final Handler mSessionStatusHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (VERBOSE) {
                Log.v(TAG, "Handler(): got msg=" + msg.what);
            }

            switch (msg.what) {
                case START_LISTENER:
                    if (mAdapter.isEnabled()) {
                        startRfcommSocketListener();
                    }
                    break;
                case USER_TIMEOUT:
                    if (mIsWaitingAuthorization) {
                        sendCancelUserConfirmationIntent(mRemoteDevice);
                        cancelUserTimeoutAlarm();
                        mIsWaitingAuthorization = false;
                        stopSapServerSession(); // And restart RfcommListener if needed
                    }
                    break;
                case MSG_SERVERSESSION_CLOSE:
                    stopSapServerSession();
                    break;
                case MSG_SESSION_ESTABLISHED:
                    break;
                case MSG_SESSION_DISCONNECTED:
                    // handled elsewhere
                    break;
                case MSG_ACQUIRE_WAKE_LOCK:
                    if (VERBOSE) {
                        Log.i(TAG, "Acquire Wake Lock request message");
                    }
                    if (mWakeLock == null) {
                        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                                "StartingObexMapTransaction");
                        mWakeLock.setReferenceCounted(false);
                    }
                    if (!mWakeLock.isHeld()) {
                        mWakeLock.acquire();
                        if (DEBUG) {
                            Log.i(TAG, "  Acquired Wake Lock by message");
                        }
                    }
                    mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
                    mSessionStatusHandler.sendMessageDelayed(
                            mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
                            RELEASE_WAKE_LOCK_DELAY);
                    break;
                case MSG_RELEASE_WAKE_LOCK:
                    if (VERBOSE) {
                        Log.i(TAG, "Release Wake Lock request message");
                    }
                    if (mWakeLock != null) {
                        mWakeLock.release();
                        if (DEBUG) {
                            Log.i(TAG, "  Released Wake Lock by message");
                        }
                    }
                    break;
                case MSG_CHANGE_STATE:
                    if (DEBUG) {
                        Log.d(TAG, "change state message: newState = " + msg.arg1);
                    }
                    setState(msg.arg1);
                    break;
                case SHUTDOWN:
                    /* Ensure to call close from this handler to avoid starting new stuff
                       because of pending messages */
                    closeService();
                    break;
                default:
                    break;
            }
        }
    };

    private void setState(int state) {
        setState(state, BluetoothSap.RESULT_SUCCESS);
    }

    private synchronized void setState(int state, int result) {
        if (state != mState) {
            if (DEBUG) {
                Log.d(TAG, "Sap state " + mState + " -> " + state + ", result = " + result);
            }
            if (state == BluetoothProfile.STATE_CONNECTED) {
                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.SAP);
            }
            int prevState = mState;
            mState = state;
            Intent intent = new Intent(BluetoothSap.ACTION_CONNECTION_STATE_CHANGED);
            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
            intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
            sendBroadcast(intent, BLUETOOTH_PERM);
        }
    }

    public int getState() {
        return mState;
    }

    public BluetoothDevice getRemoteDevice() {
        return mRemoteDevice;
    }

    public static String getRemoteDeviceName() {
        return sRemoteDeviceName;
    }

    public boolean disconnect(BluetoothDevice device) {
        boolean result = false;
        synchronized (SapService.this) {
            if (mRemoteDevice != null && mRemoteDevice.equals(device)) {
                switch (mState) {
                    case BluetoothSap.STATE_CONNECTED:
                        closeConnectionSocket();
                        setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
                        result = true;
                        break;
                    default:
                        break;
                }
            }
        }
        return result;
    }

    public List<BluetoothDevice> getConnectedDevices() {
        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
        synchronized (this) {
            if (mState == BluetoothSap.STATE_CONNECTED && mRemoteDevice != null) {
                devices.add(mRemoteDevice);
            }
        }
        return devices;
    }

    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
        int connectionState;
        synchronized (this) {
            for (BluetoothDevice device : bondedDevices) {
                ParcelUuid[] featureUuids = device.getUuids();
                if (!BluetoothUuid.containsAnyUuid(featureUuids, SAP_UUIDS)) {
                    continue;
                }
                connectionState = getConnectionState(device);
                for (int i = 0; i < states.length; i++) {
                    if (connectionState == states[i]) {
                        deviceList.add(device);
                    }
                }
            }
        }
        return deviceList;
    }

    public int getConnectionState(BluetoothDevice device) {
        synchronized (this) {
            if (getState() == BluetoothSap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
                return BluetoothProfile.STATE_CONNECTED;
            } else {
                return BluetoothProfile.STATE_DISCONNECTED;
            }
        }
    }

    /**
     * Set connection policy of the profile and disconnects it if connectionPolicy is
     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
     *
     * <p> The device should already be paired.
     * Connection policy can be one of:
     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
     *
     * @param device Paired bluetooth device
     * @param connectionPolicy is the connection policy to set to for this profile
     * @return true if connectionPolicy is set, false on error
     */
    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
        if (DEBUG) {
            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
        }
        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                "Need BLUETOOTH_PRIVILEGED permission");
        AdapterService.getAdapterService().getDatabase()
                .setProfileConnectionPolicy(device, BluetoothProfile.SAP, connectionPolicy);
        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
            disconnect(device);
        }
        return true;
    }

    /**
     * Get the connection policy of the profile.
     *
     * <p> The connection policy can be any of:
     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
     *
     * @param device Bluetooth device
     * @return connection policy of the device
     * @hide
     */
    public int getConnectionPolicy(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                "Need BLUETOOTH_PRIVILEGED permission");
        return AdapterService.getAdapterService().getDatabase()
                .getProfileConnectionPolicy(device, BluetoothProfile.SAP);
    }

    @Override
    protected IProfileServiceBinder initBinder() {
        return new SapBinder(this);
    }

    @Override
    protected boolean start() {
        Log.v(TAG, "start()");
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);

        try {
            registerReceiver(mSapReceiver, filter);
            mIsRegistered = true;
        } catch (Exception e) {
            Log.w(TAG, "Unable to register sap receiver", e);
        }
        mInterrupted = false;
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        // start RFCOMM listener
        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
        setSapService(this);
        return true;
    }

    @Override
    protected boolean stop() {
        Log.v(TAG, "stop()");
        if (!mIsRegistered) {
            Log.i(TAG, "Avoid unregister when receiver it is not registered");
            return true;
        }
        setSapService(null);
        try {
            mIsRegistered = false;
            unregisterReceiver(mSapReceiver);
        } catch (Exception e) {
            Log.w(TAG, "Unable to unregister sap receiver", e);
        }
        setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
        sendShutdownMessage();
        return true;
    }

    @Override
    public void cleanup() {
        setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
        closeService();
        if (mSessionStatusHandler != null) {
            mSessionStatusHandler.removeCallbacksAndMessages(null);
        }
    }

    /**
     * Get the current instance of {@link SapService}
     *
     * @return current instance of {@link SapService}
     */
    @VisibleForTesting
    public static synchronized SapService getSapService() {
        if (sSapService == null) {
            Log.w(TAG, "getSapService(): service is null");
            return null;
        }
        if (!sSapService.isAvailable()) {
            Log.w(TAG, "getSapService(): service is not available");
            return null;
        }
        return sSapService;
    }

    private static synchronized void setSapService(SapService instance) {
        if (DEBUG) {
            Log.d(TAG, "setSapService(): set to: " + instance);
        }
        sSapService = instance;
    }

    private void setUserTimeoutAlarm() {
        if (DEBUG) {
            Log.d(TAG, "setUserTimeOutAlarm()");
        }
        cancelUserTimeoutAlarm();
        mRemoveTimeoutMsg = true;
        Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
    }

    private void cancelUserTimeoutAlarm() {
        if (DEBUG) {
            Log.d(TAG, "cancelUserTimeOutAlarm()");
        }
        if (mAlarmManager == null) {
            mAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
        }
        if (mRemoveTimeoutMsg) {
            Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
            PendingIntent sender = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
            mAlarmManager.cancel(sender);
            mRemoveTimeoutMsg = false;
        }
    }

    private void sendCancelUserConfirmationIntent(BluetoothDevice device) {
        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
        intent.setPackage(getString(R.string.pairing_ui_package));
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
        sendBroadcast(intent, BLUETOOTH_PERM);
    }

    private void sendShutdownMessage() {
        /* Any pending messages are no longer valid.
        To speed up things, simply delete them. */
        if (mRemoveTimeoutMsg) {
            Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
            sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
            mIsWaitingAuthorization = false;
            cancelUserTimeoutAlarm();
        }
        removeSdpRecord();
        mSessionStatusHandler.removeCallbacksAndMessages(null);
        // Request release of all resources
        mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
    }

    private void sendConnectTimeoutMessage() {
        if (DEBUG) {
            Log.d(TAG, "sendConnectTimeoutMessage()");
        }
        if (mSessionStatusHandler != null) {
            Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT);
            msg.sendToTarget();
        } // Can only be null during shutdown
    }

    private SapBroadcastReceiver mSapReceiver = new SapBroadcastReceiver();

    private class SapBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {

            if (VERBOSE) {
                Log.v(TAG, "onReceive");
            }
            String action = intent.getAction();
            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                int state =
                        intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
                if (state == BluetoothAdapter.STATE_TURNING_OFF) {
                    if (DEBUG) {
                        Log.d(TAG, "STATE_TURNING_OFF");
                    }
                    sendShutdownMessage();
                } else if (state == BluetoothAdapter.STATE_ON) {
                    if (DEBUG) {
                        Log.d(TAG, "STATE_ON");
                    }
                    // start RFCOMM listener
                    mSessionStatusHandler.sendMessage(
                            mSessionStatusHandler.obtainMessage(START_LISTENER));
                }
                return;
            }

            if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
                Log.v(TAG, " - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");

                int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, -1);
                if (requestType != BluetoothDevice.REQUEST_TYPE_SIM_ACCESS) {
                    return;
                }

                mIsWaitingAuthorization = false;

                if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
                        BluetoothDevice.CONNECTION_ACCESS_NO)
                        == BluetoothDevice.CONNECTION_ACCESS_YES) {
                    // bluetooth connection accepted by user
                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
                        boolean result = mRemoteDevice.setSimAccessPermission(
                                BluetoothDevice.ACCESS_ALLOWED);
                        if (VERBOSE) {
                            Log.v(TAG, "setSimAccessPermission(ACCESS_ALLOWED) result=" + result);
                        }
                    }
                    boolean result = setConnectionPolicy(mRemoteDevice,
                            BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                    Log.d(TAG, "setConnectionPolicy ALLOWED, result = " + result);

                    try {
                        if (mConnSocket != null) {
                            // start obex server and rfcomm connection
                            startSapServerSession();
                        } else {
                            stopSapServerSession();
                        }
                    } catch (IOException ex) {
                        Log.e(TAG, "Caught the error: ", ex);
                    }
                } else {
                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
                        boolean result = mRemoteDevice.setSimAccessPermission(
                                BluetoothDevice.ACCESS_REJECTED);
                        if (VERBOSE) {
                            Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result=" + result);
                        }
                    }
                    boolean result = setConnectionPolicy(mRemoteDevice,
                            BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
                    Log.d(TAG, "setConnectionPolicy FORBIDDEN, result = " + result);
                    // Ensure proper cleanup, and prepare for new connect.
                    mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
                }
                return;
            }

            if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)) {
                if (DEBUG) {
                    Log.d(TAG, "USER_CONFIRM_TIMEOUT_ACTION Received.");
                }
                // send us self a message about the timeout.
                sendConnectTimeoutMessage();
                return;
            }

            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                if (mRemoteDevice == null || device == null) {
                    Log.i(TAG, "Unexpected error!");
                    return;
                }

                if (DEBUG) {
                    Log.d(TAG, "ACL disconnected for " + device);
                }

                if (mRemoteDevice.equals(device)) {
                    if (mRemoveTimeoutMsg) {
                        // Send any pending timeout now, as ACL got disconnected.
                        mSessionStatusHandler.removeMessages(USER_TIMEOUT);
                        mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
                    }
                    setState(BluetoothSap.STATE_DISCONNECTED);
                    // Ensure proper cleanup, and prepare for new connect.
                    mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
                }
            }
        }
    }

    ;

    //Binder object: Must be static class or memory leak may occur

    /**
     * This class implements the IBluetoothSap interface - or actually it validates the
     * preconditions for calling the actual functionality in the SapService, and calls it.
     */
    private static class SapBinder extends IBluetoothSap.Stub implements IProfileServiceBinder {
        private SapService mService;

        private SapService getService() {
            if (!Utils.checkCaller()) {
                Log.w(TAG, "call not allowed for non-active user");
                return null;
            }

            if (mService != null && mService.isAvailable()) {
                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
                        "Need BLUETOOTH permission");
                return mService;
            }
            return null;
        }

        SapBinder(SapService service) {
            Log.v(TAG, "SapBinder()");
            mService = service;
        }

        @Override
        public void cleanup() {
            mService = null;
        }

        @Override
        public int getState() {
            Log.v(TAG, "getState()");
            SapService service = getService();
            if (service == null) {
                return BluetoothSap.STATE_DISCONNECTED;
            }
            return getService().getState();
        }

        @Override
        public BluetoothDevice getClient() {
            Log.v(TAG, "getClient()");
            SapService service = getService();
            if (service == null) {
                return null;
            }
            Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
            return service.getRemoteDevice();
        }

        @Override
        public boolean isConnected(BluetoothDevice device) {
            Log.v(TAG, "isConnected()");
            SapService service = getService();
            if (service == null) {
                return false;
            }
            return (service.getState() == BluetoothSap.STATE_CONNECTED && service.getRemoteDevice()
                    .equals(device));
        }

        @Override
        public boolean connect(BluetoothDevice device) {
            Log.v(TAG, "connect()");
            SapService service = getService();
            if (service == null) {
                return false;
            }
            return false;
        }

        @Override
        public boolean disconnect(BluetoothDevice device) {
            Log.v(TAG, "disconnect()");
            SapService service = getService();
            if (service == null) {
                return false;
            }
            return service.disconnect(device);
        }

        @Override
        public List<BluetoothDevice> getConnectedDevices() {
            Log.v(TAG, "getConnectedDevices()");
            SapService service = getService();
            if (service == null) {
                return new ArrayList<BluetoothDevice>(0);
            }
            return service.getConnectedDevices();
        }

        @Override
        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
            Log.v(TAG, "getDevicesMatchingConnectionStates()");
            SapService service = getService();
            if (service == null) {
                return new ArrayList<BluetoothDevice>(0);
            }
            return service.getDevicesMatchingConnectionStates(states);
        }

        @Override
        public int getConnectionState(BluetoothDevice device) {
            Log.v(TAG, "getConnectionState()");
            SapService service = getService();
            if (service == null) {
                return BluetoothProfile.STATE_DISCONNECTED;
            }
            return service.getConnectionState(device);
        }

        @Override
        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
            SapService service = getService();
            if (service == null) {
                return false;
            }
            return service.setConnectionPolicy(device, connectionPolicy);
        }

        @Override
        public int getConnectionPolicy(BluetoothDevice device) {
            SapService service = getService();
            if (service == null) {
                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
            }
            return service.getConnectionPolicy(device);
        }
    }
}
