/*
 * Copyright (C) 2017 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.
 */
/*
 * Copyright (c) 2015-2017, The Linux Foundation.
 */

/*
 * Contributed by: Giesecke & Devrient GmbH.
 */

package com.android.se;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.secure_element.V1_0.ISecureElement;
import android.hardware.secure_element.V1_0.ISecureElementHalCallback;
import android.hardware.secure_element.V1_0.LogicalChannelResponse;
import android.hardware.secure_element.V1_0.SecureElementStatus;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HwBinder;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.se.omapi.ISecureElementListener;
import android.se.omapi.ISecureElementReader;
import android.se.omapi.ISecureElementSession;
import android.se.omapi.SEService;
import android.util.Log;

import com.android.se.SecureElementService.SecureElementSession;
import com.android.se.internal.ByteArrayConverter;
import com.android.se.security.AccessControlEnforcer;
import com.android.se.security.ChannelAccess;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.NoSuchElementException;

/**
 * Each Terminal represents a Secure Element.
 * Communicates to the SE via SecureElement HAL.
 */
public class Terminal {

    private final String mTag;
    private final Map<Integer, Channel> mChannels = new HashMap<Integer, Channel>();
    private final Object mLock = new Object();
    private final String mName;
    public boolean mIsConnected = false;
    private Context mContext;
    private boolean mDefaultApplicationSelectedOnBasicChannel = true;

    private static final boolean DEBUG = Build.isDebuggable();
    private static final int GET_SERVICE_DELAY_MILLIS = 4 * 1000;
    private static final int EVENT_GET_HAL = 1;

    private final int mMaxGetHalRetryCount = 5;
    private int mGetHalRetryCount = 0;

    private ISecureElement mSEHal;
    private android.hardware.secure_element.V1_2.ISecureElement mSEHal12;
    private android.hardware.secure_element.ISecureElement mAidlHal;

    /** For each Terminal there will be one AccessController object. */
    private AccessControlEnforcer mAccessControlEnforcer;

    private static final String SECURE_ELEMENT_PRIVILEGED_OPERATION_PERMISSION =
            "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";

    public static final byte[] ISD_R_AID =
            new byte[]{
                    (byte) 0xA0,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x05,
                    (byte) 0x59,
                    (byte) 0x10,
                    (byte) 0x10,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0x89,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x01,
                    (byte) 0x00,
            };

    private ISecureElementHalCallback.Stub mHalCallback = new ISecureElementHalCallback.Stub() {
        @Override
        public void onStateChange(boolean state) {
            stateChange(state, "");
        }
    };

    private android.hardware.secure_element.V1_1.ISecureElementHalCallback.Stub mHalCallback11 =
            new android.hardware.secure_element.V1_1.ISecureElementHalCallback.Stub() {
        @Override
        public void onStateChange_1_1(boolean state, String reason) {
            stateChange(state, reason);
        }

        public void onStateChange(boolean state) {
            return;
        }
    };

    private android.hardware.secure_element.ISecureElementCallback.Stub mAidlCallback =
                new android.hardware.secure_element.ISecureElementCallback.Stub() {
        @Override
        public void onStateChange(boolean state, String debugReason) {
            stateChange(state, debugReason);
        }

        @Override
        public int getInterfaceVersion() {
            return super.VERSION;
        }

        @Override
        public String getInterfaceHash() {
            return super.HASH;
        }
    };

    private void stateChange(boolean state, String reason) {
        synchronized (mLock) {
            Log.i(mTag, "OnStateChange:" + state + " reason:" + reason);
            mIsConnected = state;
            if (!state) {
                if (mAccessControlEnforcer != null) {
                    mAccessControlEnforcer.reset();
                }
                SecureElementStatsLog.write(
                        SecureElementStatsLog.SE_STATE_CHANGED,
                        SecureElementStatsLog.SE_STATE_CHANGED__STATE__DISCONNECTED,
                        reason,
                        mName);
            } else {
                // If any logical channel in use is in the channel list, it should be closed
                // because the access control enfocer allowed to open it by checking the access
                // rules retrieved before. Now we are going to retrieve the rules again and
                // the new rules can be different from the previous ones.
                closeChannels();
                try {
                    initializeAccessControl();
                } catch (Exception e) {
                    // ignore
                }
                mDefaultApplicationSelectedOnBasicChannel = true;
                SecureElementStatsLog.write(
                        SecureElementStatsLog.SE_STATE_CHANGED,
                        SecureElementStatsLog.SE_STATE_CHANGED__STATE__CONNECTED,
                        reason,
                        mName);
            }

            sendStateChangedBroadcast(state);
        }
    }

    private void sendStateChangedBroadcast(boolean state) {
        Intent intent = new Intent(SEService.ACTION_SECURE_ELEMENT_STATE_CHANGED);
        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        intent.putExtra(SEService.EXTRA_READER_NAME, mName);
        intent.putExtra(SEService.EXTRA_READER_STATE, state);
        mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
    };

    class SecureElementDeathRecipient implements HwBinder.DeathRecipient, Binder.DeathRecipient {
        // for AIDL
        @Override
        public void binderDied() {
            onDied();
        }

        // for HIDL
        @Override
        public void serviceDied(long cookie) {
            onDied();
        }

        private void onDied() {
            Log.e(mTag, mName + " died");
            SecureElementStatsLog.write(
                    SecureElementStatsLog.SE_STATE_CHANGED,
                    SecureElementStatsLog.SE_STATE_CHANGED__STATE__HALCRASH,
                    "HALCRASH",
                    mName);
            synchronized (mLock) {
                mIsConnected = false;
                if (mAccessControlEnforcer != null) {
                    mAccessControlEnforcer.reset();
                }
            }
            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_GET_HAL, 0),
                    GET_SERVICE_DELAY_MILLIS);
        }
    }

    private SecureElementDeathRecipient mDeathRecipient = new SecureElementDeathRecipient();

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case EVENT_GET_HAL:
                    try {
                        if (mName.startsWith(SecureElementService.ESE_TERMINAL)) {
                            initialize(true);
                        } else {
                            initialize(false);
                        }
                    } catch (Exception e) {
                        Log.e(mTag, mName + " could not be initialized again");
                        if (mGetHalRetryCount < mMaxGetHalRetryCount) {
                            mGetHalRetryCount++;
                            sendMessageDelayed(obtainMessage(EVENT_GET_HAL, 0),
                                    GET_SERVICE_DELAY_MILLIS);
                        } else {
                            Log.e(mTag, mName + " reach maximum retry count");
                        }
                    }
                    break;
                default:
                    break;
            }
        }
    };

    public Terminal(String name, Context context) {
        mContext = context;
        mName = name;
        mTag = "SecureElement-Terminal-" + getName();
    }

    /**
     * Initializes the terminal
     *
     * @throws NoSuchElementException if there is no HAL implementation for the specified SE name
     * @throws RemoteException if there is a failure communicating with the remote
     */
    public void initialize(boolean retryOnFail) throws NoSuchElementException, RemoteException {
        android.hardware.secure_element.V1_1.ISecureElement mSEHal11 = null;
        synchronized (mLock) {
            try {
                String name = "android.hardware.secure_element.ISecureElement/" + mName;
                IBinder binder = null;
                if (retryOnFail) {
                    binder = ServiceManager.waitForDeclaredService(name);
                } else {
                    if (ServiceManager.isDeclared(name)) {
                        binder = ServiceManager.getService(name);
                    }
                }
                mAidlHal = android.hardware.secure_element.ISecureElement.Stub.asInterface(binder);
            } catch (Exception e) {
                Log.d(mTag, "SE AIDL Hal is not supported");
            }

            if (mAidlHal == null) {
                try {
                    mSEHal = mSEHal11 = mSEHal12 =
                            android.hardware.secure_element.V1_2.ISecureElement.getService(
                                    mName, retryOnFail);
                } catch (Exception e) {
                    Log.d(mTag, "SE Hal V1.2 is not supported");
                }
            }

            if (mSEHal12 == null && mAidlHal == null) {
                try {
                    mSEHal = mSEHal11 =
                            android.hardware.secure_element.V1_1.ISecureElement.getService(
                                    mName, retryOnFail);
                } catch (Exception e) {
                    Log.d(mTag, "SE Hal V1.1 is not supported");
                }

                if (mSEHal11 == null) {
                    mSEHal = ISecureElement.getService(mName, retryOnFail);
                    if (mSEHal == null) {
                        throw new NoSuchElementException("No HAL is provided for " + mName);
                    }
                }
            }
            if (mAidlHal != null) {
                mAidlHal.init(mAidlCallback);
                mAidlHal.asBinder().linkToDeath(mDeathRecipient, 0);
            } else if (mSEHal11 != null || mSEHal12 != null) {
                mSEHal11.init_1_1(mHalCallback11);
                mSEHal.linkToDeath(mDeathRecipient, 0);
            } else {
                mSEHal.init(mHalCallback);
                mSEHal.linkToDeath(mDeathRecipient, 0);
            }
        }
        Log.i(mTag, mName + " was initialized");
        SecureElementStatsLog.write(
                SecureElementStatsLog.SE_STATE_CHANGED,
                SecureElementStatsLog.SE_STATE_CHANGED__STATE__INITIALIZED,
                "INIT",
                mName);
    }

    private ArrayList<Byte> byteArrayToArrayList(byte[] array) {
        ArrayList<Byte> list = new ArrayList<Byte>();
        if (array == null) {
            return list;
        }

        for (Byte b : array) {
            list.add(b);
        }
        return list;
    }

    private byte[] arrayListToByteArray(ArrayList<Byte> list) {
        Byte[] byteArray = list.toArray(new Byte[list.size()]);
        int i = 0;
        byte[] result = new byte[list.size()];
        for (Byte b : byteArray) {
            result[i++] = b.byteValue();
        }
        return result;
    }

    /**
     * Closes the given channel
     */
    public void closeChannel(Channel channel) {
        if (channel == null) {
            return;
        }
        synchronized (mLock) {
            if (mIsConnected) {
                try {
                    int status = 0;
                    if (mAidlHal != null) {
                        try {
                            mAidlHal.closeChannel((byte) channel.getChannelNumber());
                        } catch (ServiceSpecificException e) {
                            status = e.errorCode;
                        }
                    } else {
                        status = mSEHal.closeChannel((byte) channel.getChannelNumber());
                    }
                    /* For Basic Channels, errors are expected.
                     * Underlying implementations use this call as an indication when there
                     * aren't any users actively using the channel, and the chip can go
                     * into low power state.
                     */
                    if (!channel.isBasicChannel() && status != SecureElementStatus.SUCCESS) {
                        Log.e(mTag, "Error closing channel " + channel.getChannelNumber());
                    }
                } catch (RemoteException e) {
                    Log.e(mTag, "Exception in closeChannel() " + e);
                }
            }
            mChannels.remove(channel.getChannelNumber(), channel);
            if (mChannels.get(channel.getChannelNumber()) != null) {
                Log.e(mTag, "Removing channel failed");
            }
        }
    }

    /**
     * Cleans up all the channels in use.
     */
    public void closeChannels() {
        synchronized (mLock) {
            Collection<Channel> col = mChannels.values();
            Channel[] channelList = col.toArray(new Channel[col.size()]);
            for (Channel channel : channelList) {
                channel.close();
            }
        }
    }

    /**
     * Closes the terminal.
     */
    public void close() {
        synchronized (mLock) {
            if (mAidlHal != null) {
                try {
                    mAidlHal.asBinder().unlinkToDeath(mDeathRecipient, 0);
                } catch (Exception e) {
                    // ignore
                }
            }
            if (mSEHal != null) {
                try {
                    mSEHal.unlinkToDeath(mDeathRecipient);
                } catch (RemoteException e) {
                    // ignore
                }
            }
        }
    }

    public String getName() {
        return mName;
    }

    /**
     * Returns the ATR of the Secure Element, or null if not available.
     */
    public byte[] getAtr() {
        if (!mIsConnected) {
            return null;
        }

        try {
            byte[] atr;
            if (mAidlHal != null) {
                atr = mAidlHal.getAtr();
                if (atr.length == 0) {
                    return null;
                }
            } else {
                ArrayList<Byte> responseList = mSEHal.getAtr();
                if (responseList.isEmpty()) {
                    return null;
                }
                atr = arrayListToByteArray(responseList);
            }
            if (DEBUG) {
                Log.i(mTag, "ATR : " + ByteArrayConverter.byteArrayToHexString(atr));
            }
            return atr;
        } catch (RemoteException e) {
            Log.e(mTag, "Exception in getAtr()" + e);
            return null;
        } catch (ServiceSpecificException e) {
            Log.e(mTag, "Exception in getAtr()" + e);
            return null;
        }
    }

    /**
     * Selects the default application on the basic channel.
     *
     * If there is an exception selecting the default application, select
     * is performed with the default access control aid.
     */
    public void selectDefaultApplication() {
        try {
            select(null);
        } catch (NoSuchElementException e) {
            if (getAccessControlEnforcer() != null) {
                try {
                    select(mAccessControlEnforcer.getDefaultAccessControlAid());
                } catch (Exception ignore) {
                }
            }
        } catch (Exception ignore) {
        }
    }

    private void select(byte[] aid) throws IOException {
        int commandSize = (aid == null ? 0 : aid.length) + 5;
        byte[] selectCommand = new byte[commandSize];
        selectCommand[0] = 0x00;
        selectCommand[1] = (byte) 0xA4;
        selectCommand[2] = 0x04;
        selectCommand[3] = 0x00;
        if (aid != null && aid.length != 0) {
            selectCommand[4] = (byte) aid.length;
            System.arraycopy(aid, 0, selectCommand, 5, aid.length);
        } else {
            selectCommand[4] = 0x00;
        }
        byte[] selectResponse = transmit(selectCommand);
        if (selectResponse.length < 2) {
            selectResponse = null;
            throw new NoSuchElementException("Response length is too small");
        }
        int sw1 = selectResponse[selectResponse.length - 2] & 0xFF;
        int sw2 = selectResponse[selectResponse.length - 1] & 0xFF;
        if (sw1 != 0x90 || sw2 != 0x00) {
            selectResponse = null;
            throw new NoSuchElementException("Status word is incorrect");
        }
    }

    /**
     * Opens a Basic Channel with the given AID and P2 paramters
     * with the given device app reference package name or uuid
     */
    public Channel openBasicChannel(SecureElementSession session, byte[] aid, byte p2,
            ISecureElementListener listener, String packageName, byte[] uuid,
            int pid) throws IOException,
            NoSuchElementException {
        if (aid != null && aid.length == 0) {
            aid = null;
        } else if (aid != null && (aid.length < 5 || aid.length > 16)) {
            throw new IllegalArgumentException("AID out of range");
        } else if (!mIsConnected) {
            throw new IOException("Secure Element is not connected");
        }

        ChannelAccess channelAccess = null;
        if (packageName != null) {
            Log.w(mTag, "Enable access control on basic channel for package name: "
                    + packageName);
            SecureElementStatsLog.write(
                    SecureElementStatsLog.SE_OMAPI_REPORTED,
                    SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
                    mName,
                    packageName);
        } else if (uuid != null) {
            Log.w(mTag, "Enable access control on basic channel for uid: "
                    + Binder.getCallingUid()
                    + " UUID: " + Arrays.toString(uuid));
            SecureElementStatsLog.write(
                    SecureElementStatsLog.SE_OMAPI_REPORTED,
                    SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
                    mName,
                    Arrays.toString(uuid));
        }
        try {
            // For application without privilege permission or carrier privilege,
            // openBasicChannel with UICC terminals should be rejected.
            if (packageName != null || uuid != null) {
                channelAccess = setUpChannelAccess(aid, packageName, uuid, pid, true);
            }
        } catch (MissingResourceException e) {
            return null;
        }

        synchronized (mLock) {
            if (mChannels.get(0) != null) {
                Log.e(mTag, "basic channel in use");
                return null;
            }
            if (aid == null && !mDefaultApplicationSelectedOnBasicChannel) {
                Log.e(mTag, "default application is not selected");
                return null;
            }

            ArrayList<byte[]> responseList = new ArrayList<byte[]>();
            int[] status = new int[1];
            status[0] = 0;

            if (mAidlHal != null) {
                try {
                    responseList.add(mAidlHal.openBasicChannel(
                                aid == null ? new byte[0] : aid, p2));
                } catch (RemoteException e) {
                    throw new IOException(e.getMessage());
                } catch (ServiceSpecificException e) {
                    status[0] = e.errorCode;
                }
            } else {
                try {
                    mSEHal.openBasicChannel(byteArrayToArrayList(aid), p2,
                            new ISecureElement.openBasicChannelCallback() {
                                @Override
                                public void onValues(ArrayList<Byte> responseObject,
                                                     byte halStatus) {
                                    status[0] = halStatus;
                                    responseList.add(arrayListToByteArray(responseObject));
                                    return;
                                }
                            });
                } catch (RemoteException e) {
                    throw new IOException(e.getMessage());
                }
            }

            if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
                return null;
            } else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
                throw new UnsupportedOperationException("OpenBasicChannel() failed");
            } else if (status[0] == SecureElementStatus.IOERROR) {
                throw new IOException("OpenBasicChannel() failed");
            } else if (status[0] == SecureElementStatus.NO_SUCH_ELEMENT_ERROR) {
                throw new NoSuchElementException("OpenBasicChannel() failed");
            }

            byte[] selectResponse = responseList.get(0);
            Channel basicChannel = new Channel(session, this, 0, selectResponse, aid,
                    listener);
            basicChannel.setChannelAccess(channelAccess);

            if (aid != null) {
                mDefaultApplicationSelectedOnBasicChannel = false;
            }
            mChannels.put(0, basicChannel);
            return basicChannel;
        }
    }

    /**
     * Opens a logical Channel without Channel Access initialization.
     */
    public Channel openLogicalChannelWithoutChannelAccess(byte[] aid) throws IOException,
            NoSuchElementException {
        return openLogicalChannel(null, aid, (byte) 0x00, null, null, null, 0);
    }

    /**
     * Opens a logical Channel with AID for the given package name or uuid
     */
    public Channel openLogicalChannel(SecureElementSession session, byte[] aid, byte p2,
            ISecureElementListener listener, String packageName,
            byte[] uuid, int pid) throws IOException,
            NoSuchElementException {
        if (aid != null && aid.length == 0) {
            aid = null;
        } else if (aid != null && (aid.length < 5 || aid.length > 16)) {
            throw new IllegalArgumentException("AID out of range");
        } else if (!mIsConnected) {
            throw new IOException("Secure Element is not connected");
        }

        ChannelAccess channelAccess = null;
        if (packageName != null) {
            Log.w(mTag, "Enable access control on logical channel for " + packageName);
            SecureElementStatsLog.write(
                    SecureElementStatsLog.SE_OMAPI_REPORTED,
                    SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
                    mName,
                    packageName);
        } else if (uuid != null) {
            Log.w(mTag, "Enable access control on logical channel for uid: "
                    + Binder.getCallingUid()
                    + " UUID: " + Arrays.toString(uuid));
            SecureElementStatsLog.write(
                    SecureElementStatsLog.SE_OMAPI_REPORTED,
                    SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
                    mName,
                    Arrays.toString(uuid));
        }
        try {
            if (packageName != null || uuid != null) {
                channelAccess = setUpChannelAccess(aid, packageName, uuid, pid, false);
            }
        } catch (MissingResourceException | UnsupportedOperationException e) {
            return null;
        }

        synchronized (mLock) {
            LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
            int[] status = new int[1];
            status[0] = 0;

            if (mAidlHal != null) {
                try {
                    responseArray[0] = new LogicalChannelResponse();
                    android.hardware.secure_element.LogicalChannelResponse aidlRs =
                            mAidlHal.openLogicalChannel(aid == null ? new byte[0] : aid, p2);
                    responseArray[0].channelNumber = aidlRs.channelNumber;
                    responseArray[0].selectResponse = byteArrayToArrayList(aidlRs.selectResponse);
                } catch (RemoteException e) {
                    throw new IOException(e.getMessage());
                } catch (ServiceSpecificException e) {
                    status[0] = e.errorCode;
                }
            } else {
                try {
                    mSEHal.openLogicalChannel(byteArrayToArrayList(aid), p2,
                            new ISecureElement.openLogicalChannelCallback() {
                                @Override
                                public void onValues(LogicalChannelResponse response,
                                                     byte halStatus) {
                                    status[0] = halStatus;
                                    responseArray[0] = response;
                                    return;
                                }
                            });
                } catch (RemoteException e) {
                    throw new IOException(e.getMessage());
                }
            }

            if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
                return null;
            } else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
                throw new UnsupportedOperationException("OpenLogicalChannel() failed");
            } else if (status[0] == SecureElementStatus.IOERROR) {
                throw new IOException("OpenLogicalChannel() failed");
            } else if (status[0] == SecureElementStatus.NO_SUCH_ELEMENT_ERROR) {
                throw new NoSuchElementException("OpenLogicalChannel() failed");
            }
            if (responseArray[0].channelNumber <= 0 || status[0] != SecureElementStatus.SUCCESS) {
                return null;
            }
            int channelNumber = responseArray[0].channelNumber;
            byte[] selectResponse = arrayListToByteArray(responseArray[0].selectResponse);
            Channel logicalChannel = new Channel(session, this, channelNumber,
                    selectResponse, aid, listener);
            logicalChannel.setChannelAccess(channelAccess);
            mChannels.put(channelNumber, logicalChannel);
            return logicalChannel;
        }
    }

    /**
     * Returns true if the given AID can be selected on the Terminal
     */
    public boolean isAidSelectable(byte[] aid) {
        if (aid == null) {
            throw new NullPointerException("aid must not be null");
        } else if (!mIsConnected) {
            Log.e(mTag, "Secure Element is not connected");
            return false;
        }

        synchronized (mLock) {
            if (mAidlHal != null) {
                try {
                    android.hardware.secure_element.LogicalChannelResponse aidlRs =
                            mAidlHal.openLogicalChannel(aid, (byte) 0x00);
                    mAidlHal.closeChannel(aidlRs.channelNumber);
                } catch (RemoteException e) {
                    return false;
                } catch (ServiceSpecificException e) {
                    return false;
                }
                return true;
            }

            LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
            byte[] status = new byte[1];
            try {
                mSEHal.openLogicalChannel(byteArrayToArrayList(aid), (byte) 0x00,
                        new ISecureElement.openLogicalChannelCallback() {
                            @Override
                            public void onValues(LogicalChannelResponse response, byte halStatus) {
                                status[0] = halStatus;
                                responseArray[0] = response;
                                return;
                            }
                        });
                if (status[0] == SecureElementStatus.SUCCESS) {
                    mSEHal.closeChannel(responseArray[0].channelNumber);
                    return true;
                }
                return false;
            } catch (RemoteException e) {
                Log.e(mTag, "Error in isAidSelectable() returning false" + e);
                return false;
            }
        }
    }

    /**
     * Transmits the specified command and returns the response.
     *
     * @param cmd the command APDU to be transmitted.
     * @return the response received.
     */
    public byte[] transmit(byte[] cmd) throws IOException {
        if (!mIsConnected) {
            Log.e(mTag, "Secure Element is not connected");
            throw new IOException("Secure Element is not connected");
        }

        byte[] rsp = transmitInternal(cmd);
        int sw1 = rsp[rsp.length - 2] & 0xFF;
        int sw2 = rsp[rsp.length - 1] & 0xFF;

        if (sw1 == 0x6C) {
            cmd[cmd.length - 1] = rsp[rsp.length - 1];
            rsp = transmit(cmd);
        } else if (sw1 == 0x61) {
            do {
                byte[] getResponseCmd = new byte[]{
                        cmd[0], (byte) 0xC0, 0x00, 0x00, (byte) sw2
                };
                byte[] tmp = transmitInternal(getResponseCmd);
                byte[] aux = rsp;
                rsp = new byte[aux.length + tmp.length - 2];
                System.arraycopy(aux, 0, rsp, 0, aux.length - 2);
                System.arraycopy(tmp, 0, rsp, aux.length - 2, tmp.length);
                sw1 = rsp[rsp.length - 2] & 0xFF;
                sw2 = rsp[rsp.length - 1] & 0xFF;
            } while (sw1 == 0x61);
        }
        return rsp;
    }

    private byte[] transmitInternal(byte[] cmd) throws IOException {
        byte[] rsp;
        if (mAidlHal != null) {
            try {
                rsp = mAidlHal.transmit(cmd);
                if (rsp.length == 0) {
                    throw new IOException("Error in transmit()");
                }
            } catch (RemoteException e) {
                throw new IOException(e.getMessage());
            } catch (ServiceSpecificException e) {
                throw new IOException(e.getMessage());
            }
        } else {
            ArrayList<Byte> response;
            try {
                response = mSEHal.transmit(byteArrayToArrayList(cmd));
            } catch (RemoteException e) {
                throw new IOException(e.getMessage());
            }
            if (response.isEmpty()) {
                throw new IOException("Error in transmit()");
            }
            rsp = arrayListToByteArray(response);
        }

        if (DEBUG) {
            Log.i(mTag, "Sent : " + ByteArrayConverter.byteArrayToHexString(cmd));
            Log.i(mTag, "Received : " + ByteArrayConverter.byteArrayToHexString(rsp));
        }
        return rsp;
    }

    /**
     * Checks if the application is authorized to receive the transaction event.
     */
    public boolean[] isNfcEventAllowed(PackageManager packageManager, byte[] aid,
            String[] packageNames) {
        if (!mIsConnected) {
            // Return if not connected
            return null;
        }
        // Return if the access control enforcer failed in previous attempt or no rule was found.
        if (mAccessControlEnforcer == null || mAccessControlEnforcer.isNoRuleFound()) {
            Log.i(mTag, "isNfcEventAllowed: No access rules for checking.");
            return null;
        }
        mAccessControlEnforcer.setPackageManager(packageManager);

        synchronized (mLock) {
            try {
                return mAccessControlEnforcer.isNfcEventAllowed(aid, packageNames);
            } catch (Exception e) {
                Log.i(mTag, "isNfcEventAllowed Exception: " + e.getMessage());
                return null;
            }
        }
    }

    /**
     * Returns true if the Secure Element is present
     */
    public boolean isSecureElementPresent() {
        try {
            if (mAidlHal != null) {
                return mAidlHal.isCardPresent();
            } else {
                return mSEHal.isCardPresent();
            }
        } catch (ServiceSpecificException e) {
            Log.e(mTag, "Error in isSecureElementPresent() " + e);
            return false;
        } catch (RemoteException e) {
            Log.e(mTag, "Error in isSecureElementPresent() " + e);
            return false;
        }
    }

    /**
     * Reset the Secure Element. Return true if success, false otherwise.
     */
    public boolean reset() {
        synchronized (mLock) {
            if (mSEHal12 == null && mAidlHal == null) {
                return false;
            }
            mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION,
                "Need SECURE_ELEMENT_PRIVILEGED_OPERATION permission");

            try {
                if (mAidlHal != null) {
                    mAidlHal.reset();
                    return true;
                } else {
                    byte status = mSEHal12.reset();
                    // Successfully trigger reset. HAL service should send onStateChange
                    // after secure element reset and initialization process complete
                    if (status == SecureElementStatus.SUCCESS) {
                        return true;
                    }
                    Log.e(mTag, "Error resetting terminal " + mName);
                }
            } catch (ServiceSpecificException e) {
                Log.e(mTag, "Exception in reset()" + e);
            } catch (RemoteException e) {
                Log.e(mTag, "Exception in reset()" + e);
            }
        }
        return false;
    }

    /**
     * Initialize the Access Control and set up the channel access.
     */
    private ChannelAccess setUpChannelAccess(byte[] aid, String packageName, byte[] uuid, int pid,
            boolean isBasicChannel) throws IOException, MissingResourceException {
        boolean checkRefreshTag = true;
        if (packageName != null && isPrivilegedApplication(packageName)) {
            return ChannelAccess.getPrivilegeAccess(packageName, pid);
        }
        // Attempt to initialize the access control enforcer if it failed
        // due to a kind of temporary failure or no rule was found in the previous attempt.
        // For privilege access, do not attempt to initialize the access control enforcer
        // if no rule was found in the previous attempt.
        if (mAccessControlEnforcer == null || mAccessControlEnforcer.isNoRuleFound()) {
            initializeAccessControl();
            // Just finished to initialize the access control enforcer.
            // It is too much to check the refresh tag in this case.
            checkRefreshTag = false;
        }
        mAccessControlEnforcer.setPackageManager(mContext.getPackageManager());

        // Check carrier privilege when AID is not ISD-R
        if (packageName != null && getName().startsWith(SecureElementService.UICC_TERMINAL)
                && !Arrays.equals(aid, ISD_R_AID)) {
            try {
                PackageManager pm = mContext.getPackageManager();
                if (pm != null) {
                    PackageInfo pkgInfo =
                            pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
                    // Do not check the refresh tag for carrier privilege
                    if (mAccessControlEnforcer.checkCarrierPrivilege(pkgInfo, false)) {
                        Log.i(mTag, "setUp PrivilegeAccess for CarrierPrivilegeApplication. ");
                        return ChannelAccess.getCarrierPrivilegeAccess(packageName, pid);
                    }
                }
            } catch (NameNotFoundException ne) {
                Log.e(mTag, "checkCarrierPrivilege(): packageInfo is not found. ");
            } catch (Exception e) {
                Log.e(mTag, "checkCarrierPrivilege() Exception: " + e.getMessage());
            }
            if (isBasicChannel) {
                throw new MissingResourceException("openBasicChannel is not allowed.", "", "");
            } else if (aid == null) {
                // openLogicalChannel with null aid is only allowed for privilege applications
                throw new UnsupportedOperationException(
                        "null aid is not accepted in UICC terminal.");
            }
        }

        synchronized (mLock) {
            try {
                ChannelAccess channelAccess =
                        mAccessControlEnforcer.setUpChannelAccess(aid, packageName, uuid,
                                checkRefreshTag);
                channelAccess.setCallingPid(pid);
                return channelAccess;
            } catch (IOException | MissingResourceException e) {
                throw e;
            } catch (Exception e) {
                throw new SecurityException("Exception in setUpChannelAccess()" + e);
            }
        }
    }

    /**
     * Initializes the Access Control for this Terminal
     */
    private synchronized void initializeAccessControl() throws IOException,
            MissingResourceException {
        synchronized (mLock) {
            if (mAccessControlEnforcer == null) {
                mAccessControlEnforcer = new AccessControlEnforcer(this);
            }
            try {
                mAccessControlEnforcer.initialize();
            } catch (IOException | MissingResourceException e) {
                // Retrieving access rules failed because of an IO error happened between
                // the terminal and the secure element or the lack of a logical channel available.
                // It might be a temporary failure, so the terminal shall attempt to cache
                // the access rules again later.
                mAccessControlEnforcer = null;
                throw e;
            }
        }
    }

    /**
     * Checks if Secure Element Privilege permission exists for the given package
     */
    private boolean isPrivilegedApplication(String packageName) {
        PackageManager pm = mContext.getPackageManager();
        if (pm != null) {
            return (pm.checkPermission(SECURE_ELEMENT_PRIVILEGED_OPERATION_PERMISSION,
                    packageName) == PackageManager.PERMISSION_GRANTED);
        }
        return false;
    }

    public AccessControlEnforcer getAccessControlEnforcer() {
        return mAccessControlEnforcer;
    }

    public Context getContext() {
        return mContext;
    }

    /**
     * Checks if Carrier Privilege exists for the given package
     */
    public boolean checkCarrierPrivilegeRules(PackageInfo pInfo) {
        boolean checkRefreshTag = true;
        if (mAccessControlEnforcer == null || mAccessControlEnforcer.isNoRuleFound()) {
            try {
                initializeAccessControl();
            } catch (IOException e) {
                return false;
            }
            checkRefreshTag = false;
        }
        mAccessControlEnforcer.setPackageManager(mContext.getPackageManager());

        synchronized (mLock) {
            try {
                return mAccessControlEnforcer.checkCarrierPrivilege(pInfo, checkRefreshTag);
            } catch (Exception e) {
                Log.i(mTag, "checkCarrierPrivilege() Exception: " + e.getMessage());
                return false;
            }
        }
    }

    /** Dump data for debug purpose . */
    public void dump(PrintWriter writer) {
        writer.println("SECURE ELEMENT SERVICE TERMINAL: " + mName);
        writer.println();

        writer.println("mIsConnected:" + mIsConnected);
        writer.println();

        /* Dump the list of currunlty openned channels */
        writer.println("List of open channels:");

        for (Channel channel : mChannels.values()) {
            writer.println("channel " + channel.getChannelNumber() + ": ");
            writer.println("package: " + channel.getChannelAccess().getPackageName());
            writer.println("pid: " + channel.getChannelAccess().getCallingPid());
            writer.println("aid selected: " + channel.hasSelectedAid());
            writer.println("basic channel: " + channel.isBasicChannel());
            writer.println();
        }
        writer.println();

        /* Dump ACE data */
        if (mAccessControlEnforcer != null) {
            mAccessControlEnforcer.dump(writer);
        }
    }

    // Implementation of the SecureElement Reader interface according to OMAPI.
    final class SecureElementReader extends ISecureElementReader.Stub {

        private final SecureElementService mService;
        private final ArrayList<SecureElementSession> mSessions =
                new ArrayList<SecureElementSession>();

        SecureElementReader(SecureElementService service) {
            mService = service;
        }

        public byte[] getAtr() {
            return Terminal.this.getAtr();
        }

        @Override
        public boolean isSecureElementPresent() throws RemoteException {
            return Terminal.this.isSecureElementPresent();
        }

        @Override
        public void closeSessions() {
            synchronized (mLock) {
                while (mSessions.size() > 0) {
                    try {
                        mSessions.get(0).close();
                    } catch (Exception ignore) {
                    }
                }
                mSessions.clear();
            }
        }

        public void removeSession(SecureElementSession session) {
            if (session == null) {
                throw new NullPointerException("session is null");
            }

            synchronized (mLock) {
                mSessions.remove(session);
                if (mSessions.size() == 0) {
                    mDefaultApplicationSelectedOnBasicChannel = true;
                }
            }
        }

        @Override
        public ISecureElementSession openSession() throws RemoteException {
            if (!isSecureElementPresent()) {
                throw new ServiceSpecificException(SEService.IO_ERROR,
                        "Secure Element is not present.");
            }

            synchronized (mLock) {
                SecureElementSession session = mService.new SecureElementSession(this);
                mSessions.add(session);
                return session;
            }
        }

        Terminal getTerminal() {
            return Terminal.this;
        }

        @Override
        public boolean reset() {
            return Terminal.this.reset();
        }

        @Override
        public String getInterfaceHash() {
            return ISecureElementReader.HASH;
        }

        @Override
        public int getInterfaceVersion() {
            return ISecureElementReader.VERSION;
        }
    }
}
