/*
 * 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 android.se.omapi;

import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;

import java.io.IOException;

/**
 * Instances of this class represent Secure Element Readers supported to this
 * device. These Readers can be physical devices or virtual devices. They can be
 * removable or not. They can contain Secure Element that can or cannot be
 * removed.
 *
 * @see <a href="http://globalplatform.org">GlobalPlatform Open Mobile API</a>
 */
public final class Reader {

    private static final String TAG = "OMAPI.Reader";
    private final String mName;
    private final SEService mService;
    private ISecureElementReader mReader;
    private final Object mLock = new Object();


    Reader(@NonNull SEService service, @NonNull String name, @NonNull ISecureElementReader reader) {
        if (reader == null || service == null || name == null) {
            throw new IllegalArgumentException("Parameters cannot be null");
        }
        mName = name;
        mService = service;
        mReader = reader;
    }

    /**
     * Return the name of this reader.
     * <ul>
     * <li>If this reader is a SIM reader, then its name must be "SIM[Slot]".</li>
     * <li>If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"</li>
     * <li>If the reader is a embedded SE reader, then its name must be "eSE[Slot]"</li>
     * </ul>
     * Slot is a decimal number without leading zeros. The Numbering must start with 1
     * (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...).
     * The slot number “1” for a reader is optional
     * (SIM and SIM1 are both valid for the first SIM-reader,
     * but if there are two readers then the second reader must be named SIM2).
     * This applies also for other SD or SE readers.
     *
     * @return the reader name, as a String.
     */
    public @NonNull String getName() {
        return mName;
    }

    /**
     * Connects to a Secure Element in this reader. <br>
     * This method prepares (initialises) the Secure Element for communication
     * before the Session object is returned (e.g. powers the Secure Element by
     * ICC ON if its not already on). There might be multiple sessions opened at
     * the same time on the same reader. The system ensures the interleaving of
     * APDUs between the respective sessions.
     *
     * @throws IOException if something went wrong with the communicating to the
     *             Secure Element or the reader.
     * @return a Session object to be used to create Channels.
     */
    public @NonNull Session openSession() throws IOException {
        if (!mService.isConnected()) {
            throw new IllegalStateException("service is not connected");
        }

        synchronized (mLock) {
            ISecureElementSession session;
            try {
                session = mReader.openSession();
            } catch (ServiceSpecificException e) {
                throw new IOException(e.getMessage());
            } catch (RemoteException e) {
                throw new IllegalStateException(e.getMessage());
            }
            if (session == null) {
                throw new IOException("service session is null.");
            }
            return new Session(mService, session, this);
        }
    }

    /**
     * Check if a Secure Element is present in this reader.
     *
     * @throws IllegalStateException if the service is not connected
     * @return <code>true</code> if the SE is present, <code>false</code> otherwise.
     */
    public boolean isSecureElementPresent() {
        if (!mService.isConnected()) {
            throw new IllegalStateException("service is not connected");
        }

        try {
            return mReader.isSecureElementPresent();
        } catch (RemoteException e) {
            throw new IllegalStateException("Error in isSecureElementPresent()");
        }
    }

    /**
     * Return the Secure Element service this reader is bound to.
     *
     * @return the SEService object.
     */
    public @NonNull SEService getSEService() {
        return mService;
    }

    /**
     * Close all the sessions opened on this reader.
     * All the channels opened by all these sessions will be closed.
     */
    public void closeSessions() {
        if (!mService.isConnected()) {
            Log.e(TAG, "service is not connected");
            return;
        }
        synchronized (mLock) {
            try {
                mReader.closeSessions();
            } catch (RemoteException ignore) { }
        }
    }

    /**
     * Close all the sessions opened on this reader and reset the reader.
     * All the channels opened by all these sessions will be closed.
     * @return <code>true</code> if reset success, <code>false</code> otherwise.
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION)
    public boolean reset() {
        if (!mService.isConnected()) {
            Log.e(TAG, "service is not connected");
            return false;
        }
        synchronized (mLock) {
            try {
                closeSessions();
                return mReader.reset();
            } catch (RemoteException ignore) {
                return false;
            }
        }
    }
}
