/*
 * Copyright (C) 2006 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 com.android.internal.telephony.test;


import android.os.AsyncResult;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.android.internal.telephony.BaseCommands;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.gsm.CallFailCause;
import com.android.internal.telephony.gsm.PDPContextState;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.Phone;

import java.util.ArrayList;

public final class SimulatedCommands extends BaseCommands
        implements CommandsInterface, SimulatedRadioControl {
    private final static String LOG_TAG = "SIM";

    private enum SimLockState {
        NONE,
        REQUIRE_PIN,
        REQUIRE_PUK,
        SIM_PERM_LOCKED
    }

    private enum SimFdnState {
        NONE,
        REQUIRE_PIN2,
        REQUIRE_PUK2,
        SIM_PERM_LOCKED
    }

    private final static SimLockState INITIAL_LOCK_STATE = SimLockState.NONE;
    private final static String DEFAULT_SIM_PIN_CODE = "1234";
    private final static String SIM_PUK_CODE = "12345678";
    private final static SimFdnState INITIAL_FDN_STATE = SimFdnState.NONE;
    private final static String DEFAULT_SIM_PIN2_CODE = "5678";
    private final static String SIM_PUK2_CODE = "87654321";

    //***** Instance Variables
    
    SimulatedGsmCallState simulatedCallState;
    HandlerThread mHandlerThread;
    SimLockState mSimLockedState;
    boolean mSimLockEnabled;
    int mPinUnlockAttempts;
    int mPukUnlockAttempts;
    String mPinCode;
    SimFdnState mSimFdnEnabledState;
    boolean mSimFdnEnabled;
    int mPin2UnlockAttempts;
    int mPuk2UnlockAttempts;
    int mNetworkType;
    String mPin2Code;
    boolean mSsnNotifyOn = false;

    int pausedResponseCount;
    ArrayList<Message> pausedResponses = new ArrayList<Message>();

    int nextCallFailCause = CallFailCause.NORMAL_CLEARING;
    
    //***** Constructor

    public
    SimulatedCommands() {
        super(null);  // Don't log statistics
        mHandlerThread = new HandlerThread("SimulatedCommands");
        mHandlerThread.start();
        Looper looper = mHandlerThread.getLooper();
        
        simulatedCallState = new SimulatedGsmCallState(looper);
        
        setRadioState(RadioState.RADIO_OFF);
        mSimLockedState = INITIAL_LOCK_STATE;
        mSimLockEnabled = (mSimLockedState != SimLockState.NONE);
        mPinCode = DEFAULT_SIM_PIN_CODE;
        mSimFdnEnabledState = INITIAL_FDN_STATE;
        mSimFdnEnabled = (mSimFdnEnabledState != SimFdnState.NONE);
        mPin2Code = DEFAULT_SIM_PIN2_CODE;
    }

    //***** CommandsInterface implementation

    public void getIccStatus(Message result) {
        switch (mState) {
            case SIM_READY:
                resultSuccess(result, IccStatus.ICC_READY);
                break;

            case SIM_LOCKED_OR_ABSENT:
                returnSimLockedStatus(result);
                break;

            default:
                resultSuccess(result, IccStatus.ICC_NOT_READY);
                break;
        }
    }

    private void returnSimLockedStatus(Message result) {
        switch (mSimLockedState) {
            case REQUIRE_PIN:
                Log.i(LOG_TAG, "[SimCmd] returnSimLockedStatus: ICC_PIN");
                resultSuccess(result, IccStatus.ICC_PIN);
                break;

            case REQUIRE_PUK:
                Log.i(LOG_TAG, "[SimCmd] returnSimLockedStatus: ICC_PUK");
                resultSuccess(result, IccStatus.ICC_PUK);
                break;

            default:
                Log.i(LOG_TAG,
                        "[SimCmd] returnSimLockedStatus: mSimLockedState==NONE !?");
                break;
        }
    }

    public void supplyIccPin(String pin, Message result)  {
        if (mSimLockedState != SimLockState.REQUIRE_PIN) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPin: wrong state, state=" +
                    mSimLockedState);
            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
            return;
        }

        if (pin != null && pin.equals(mPinCode)) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPin: success!");
            setRadioState(RadioState.SIM_READY);
            mPinUnlockAttempts = 0;
            mSimLockedState = SimLockState.NONE;

            if (result != null) {
                AsyncResult.forMessage(result, null, null);
                result.sendToTarget();
            }

            return;
        }

        if (result != null) {
            mPinUnlockAttempts ++;

            Log.i(LOG_TAG, "[SimCmd] supplyIccPin: failed! attempt=" +
                    mPinUnlockAttempts);
            if (mPinUnlockAttempts >= 3) {
                Log.i(LOG_TAG, "[SimCmd] supplyIccPin: set state to REQUIRE_PUK");
                mSimLockedState = SimLockState.REQUIRE_PUK;
            }

            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
        }
    }

    public void supplyIccPuk(String puk, String newPin, Message result)  {
        if (mSimLockedState != SimLockState.REQUIRE_PUK) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPuk: wrong state, state=" +
                    mSimLockedState);
            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
            return;
        }

        if (puk != null && puk.equals(SIM_PUK_CODE)) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPuk: success!");
            setRadioState(RadioState.SIM_READY);
            mSimLockedState = SimLockState.NONE;
            mPukUnlockAttempts = 0;

            if (result != null) {
                AsyncResult.forMessage(result, null, null);
                result.sendToTarget();
            }

            return;
        }

        if (result != null) {
            mPukUnlockAttempts ++;

            Log.i(LOG_TAG, "[SimCmd] supplyIccPuk: failed! attempt=" +
                    mPukUnlockAttempts);
            if (mPukUnlockAttempts >= 10) {
                Log.i(LOG_TAG, "[SimCmd] supplyIccPuk: set state to SIM_PERM_LOCKED");
                mSimLockedState = SimLockState.SIM_PERM_LOCKED;
            }

            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
        }
    }

    public void supplyIccPin2(String pin2, Message result)  {
        if (mSimFdnEnabledState != SimFdnState.REQUIRE_PIN2) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPin2: wrong state, state=" +
                    mSimFdnEnabledState);
            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
            return;
        }

        if (pin2 != null && pin2.equals(mPin2Code)) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPin2: success!");
            mPin2UnlockAttempts = 0;
            mSimFdnEnabledState = SimFdnState.NONE;

            if (result != null) {
                AsyncResult.forMessage(result, null, null);
                result.sendToTarget();
            }

            return;
        }

        if (result != null) {
            mPin2UnlockAttempts ++;

            Log.i(LOG_TAG, "[SimCmd] supplyIccPin2: failed! attempt=" +
                    mPin2UnlockAttempts);
            if (mPin2UnlockAttempts >= 3) {
                Log.i(LOG_TAG, "[SimCmd] supplyIccPin2: set state to REQUIRE_PUK2");
                mSimFdnEnabledState = SimFdnState.REQUIRE_PUK2;
            }

            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
        }
    }

    public void supplyIccPuk2(String puk2, String newPin2, Message result)  {
        if (mSimFdnEnabledState != SimFdnState.REQUIRE_PUK2) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPuk2: wrong state, state=" +
                    mSimLockedState);
            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
            return;
        }

        if (puk2 != null && puk2.equals(SIM_PUK2_CODE)) {
            Log.i(LOG_TAG, "[SimCmd] supplyIccPuk2: success!");
            mSimFdnEnabledState = SimFdnState.NONE;
            mPuk2UnlockAttempts = 0;

            if (result != null) {
                AsyncResult.forMessage(result, null, null);
                result.sendToTarget();
            }

            return;
        }

        if (result != null) {
            mPuk2UnlockAttempts ++;

            Log.i(LOG_TAG, "[SimCmd] supplyIccPuk2: failed! attempt=" +
                    mPuk2UnlockAttempts);
            if (mPuk2UnlockAttempts >= 10) {
                Log.i(LOG_TAG, "[SimCmd] supplyIccPuk2: set state to SIM_PERM_LOCKED");
                mSimFdnEnabledState = SimFdnState.SIM_PERM_LOCKED;
            }

            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
        }
    }

    public void changeIccPin(String oldPin, String newPin, Message result)  {
        if (oldPin != null && oldPin.equals(mPinCode)) {
            mPinCode = newPin;
            if (result != null) {
                AsyncResult.forMessage(result, null, null);
                result.sendToTarget();
            }

            return;
        }

        if (result != null) {
            Log.i(LOG_TAG, "[SimCmd] changeIccPin: pin failed!");

            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
        }
    }

    public void changeIccPin2(String oldPin2, String newPin2, Message result)  {
        if (oldPin2 != null && oldPin2.equals(mPin2Code)) {
            mPin2Code = newPin2;
            if (result != null) {
                AsyncResult.forMessage(result, null, null);
                result.sendToTarget();
            }

            return;
        }

        if (result != null) {
            Log.i(LOG_TAG, "[SimCmd] changeIccPin2: pin2 failed!");

            CommandException ex = new CommandException(
                    CommandException.Error.PASSWORD_INCORRECT);
            AsyncResult.forMessage(result, null, ex);
            result.sendToTarget();
        }
    }

    public void
    changeBarringPassword(String facility, String oldPwd, String newPwd, Message result) {
        unimplemented(result);
    }

    public void
    setSuppServiceNotifications(boolean enable, Message result) {
        resultSuccess(result, null);
        
        if (enable && mSsnNotifyOn) {
            Log.w(LOG_TAG, "Supp Service Notifications already enabled!");
        }
        
        mSsnNotifyOn = enable;
    }

    /**
     * (AsyncResult)response.obj).result will be an Integer representing
     * the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
     *
     * @param facility one of CB_FACILTY_*
     * @param pin password or "" if not required
     * @param serviceClass is a sum of SERVICE_CLASS_*
     */

    public void queryFacilityLock (String facility, String pin,
                                   int serviceClass, Message result) {
        if (facility != null &&
                facility.equals(CommandsInterface.CB_FACILITY_BA_SIM)) {
            if (result != null) {
                int[] r = new int[1];
                r[0] = (mSimLockEnabled ? 1 : 0);
                Log.i(LOG_TAG, "[SimCmd] queryFacilityLock: SIM is " +
                        (r[0] == 0 ? "unlocked" : "locked"));
                AsyncResult.forMessage(result, r, null);
                result.sendToTarget();
            }
            return;
        } else if (facility != null &&
                facility.equals(CommandsInterface.CB_FACILITY_BA_FD)) {
            if (result != null) {
                int[] r = new int[1];
                r[0] = (mSimFdnEnabled ? 1 : 0);
                Log.i(LOG_TAG, "[SimCmd] queryFacilityLock: FDN is " +
                        (r[0] == 0 ? "disabled" : "enabled"));
                AsyncResult.forMessage(result, r, null);
                result.sendToTarget();
            }
            return;
        }

        unimplemented(result);
    }

    /**
     * @param facility one of CB_FACILTY_*
     * @param lockEnabled true if SIM lock is enabled
     * @param pin the SIM pin or "" if not required
     * @param serviceClass is a sum of SERVICE_CLASS_*
     */
    public void setFacilityLock (String facility, boolean lockEnabled,
                                 String pin, int serviceClass,
                                 Message result) {
        if (facility != null &&
                facility.equals(CommandsInterface.CB_FACILITY_BA_SIM)) {
            if (pin != null && pin.equals(mPinCode)) {
                Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin is valid");
                mSimLockEnabled = lockEnabled;

                if (result != null) {
                    AsyncResult.forMessage(result, null, null);
                    result.sendToTarget();
                }

                return;
            }

            if (result != null) {
                Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin failed!");

                CommandException ex = new CommandException(
                        CommandException.Error.GENERIC_FAILURE);
                AsyncResult.forMessage(result, null, ex);
                result.sendToTarget();
            }

            return;
        }  else if (facility != null &&
                facility.equals(CommandsInterface.CB_FACILITY_BA_FD)) {
            if (pin != null && pin.equals(mPin2Code)) {
                Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin2 is valid");
                mSimFdnEnabled = lockEnabled;

                if (result != null) {
                    AsyncResult.forMessage(result, null, null);
                    result.sendToTarget();
                }

                return;
            }

            if (result != null) {
                Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin2 failed!");

                CommandException ex = new CommandException(
                        CommandException.Error.GENERIC_FAILURE);
                AsyncResult.forMessage(result, null, ex);
                result.sendToTarget();
            }

            return;
        }

        unimplemented(result);
    }

    public void supplyNetworkDepersonalization(String netpin, Message result)  {
        unimplemented(result);
    }

    /** 
     *  returned message
     *  retMsg.obj = AsyncResult ar
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result contains a List of DriverCall
     *      The ar.result List is sorted by DriverCall.index
     */
    public void getCurrentCalls (Message result) {
        if (mState == RadioState.SIM_READY) {
            //Log.i("GSM", "[SimCmds] getCurrentCalls");
            resultSuccess(result, simulatedCallState.getDriverCalls());
        } else {
            //Log.i("GSM", "[SimCmds] getCurrentCalls: SIM not ready!");
            resultFail(result, 
                new CommandException(
                    CommandException.Error.RADIO_NOT_AVAILABLE));
        }
    }

    /** 
     *  @deprecated
     */
    public void getPDPContextList(Message result) {
        getDataCallList(result);
    }

    /**
     *  returned message
     *  retMsg.obj = AsyncResult ar
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result contains a List of PDPContextState
     */
    public void getDataCallList(Message result) {
        resultSuccess(result, new ArrayList<PDPContextState>(0));
    }

    /** 
     *  returned message
     *  retMsg.obj = AsyncResult ar
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     *
     * CLIR_DEFAULT     == on "use subscription default value"
     * CLIR_SUPPRESSION == on "CLIR suppression" (allow CLI presentation)
     * CLIR_INVOCATION  == on "CLIR invocation" (restrict CLI presentation)
     */
    public void dial (String address, int clirMode, Message result) {
        simulatedCallState.onDial(address);

        resultSuccess(result, null);
    }

    /** 
     *  returned message
     *  retMsg.obj = AsyncResult ar
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is String containing IMSI on success
     */
    public void getIMSI(Message result) {
        resultSuccess(result, "012345678901234");
    }

    /** 
     *  returned message
     *  retMsg.obj = AsyncResult ar
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is String containing IMEI on success
     */
    public void getIMEI(Message result) {
        resultSuccess(result, "012345678901234");
    }

    /** 
     *  returned message
     *  retMsg.obj = AsyncResult ar
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is String containing IMEISV on success
     */
    public void getIMEISV(Message result) {
        resultSuccess(result, "99");
    }

    /** 
     * Hang up one individual connection.
     *  returned message
     *  retMsg.obj = AsyncResult ar
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     *
     *  3GPP 22.030 6.5.5
     *  "Releases a specific active call X"
     */
    public void hangupConnection (int gsmIndex, Message result) {
        boolean success;
        
        success = simulatedCallState.onChld('1', (char)('0'+gsmIndex));

        if (!success){
            Log.i("GSM", "[SimCmd] hangupConnection: resultFail");
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            Log.i("GSM", "[SimCmd] hangupConnection: resultSuccess");
            resultSuccess(result, null);
        }
    }

    /**
     * 3GPP 22.030 6.5.5
     *  "Releases all held calls or sets User Determined User Busy (UDUB)
     *   for a waiting call."
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */
    public void hangupWaitingOrBackground (Message result) {
        boolean success;
        
        success = simulatedCallState.onChld('0', '\0');

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /**
     * 3GPP 22.030 6.5.5
     * "Releases all active calls (if any exist) and accepts 
     *  the other (held or waiting) call."
     *
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */
    public void hangupForegroundResumeBackground (Message result) {
        boolean success;
        
        success = simulatedCallState.onChld('1', '\0');

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /**
     * 3GPP 22.030 6.5.5
     * "Places all active calls (if any exist) on hold and accepts 
     *  the other (held or waiting) call."
     *
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */
    public void switchWaitingOrHoldingAndActive (Message result) {
        boolean success;
        
        success = simulatedCallState.onChld('2', '\0');

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /**
     * 3GPP 22.030 6.5.5
     * "Adds a held call to the conversation"
     *
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */    
    public void conference (Message result) {
        boolean success;
        
        success = simulatedCallState.onChld('3', '\0');

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /**
     * 3GPP 22.030 6.5.5
     * "Connects the two calls and disconnects the subscriber from both calls"
     *
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */
    public void explicitCallTransfer (Message result) {
        boolean success;

        success = simulatedCallState.onChld('4', '\0');

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /**
     * 3GPP 22.030 6.5.5
     * "Places all active calls on hold except call X with which 
     *  communication shall be supported."
     */
    public void separateConnection (int gsmIndex, Message result) {
        boolean success;

        char ch = (char)(gsmIndex + '0');
        success = simulatedCallState.onChld('2', ch);

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /**
     *
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */    
    public void acceptCall (Message result) {
        boolean success;
        
        success = simulatedCallState.onAnswer();

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /** 
     *  also known as UDUB
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */    
    public void rejectCall (Message result) {
        boolean success;
        
        success = simulatedCallState.onChld('0', '\0');

        if (!success){
            resultFail(result, new RuntimeException("Hangup Error"));
        } else {
            resultSuccess(result, null);
        }
    }

    /** 
     * cause code returned as Integer in Message.obj.response
     * Returns integer cause code defined in TS 24.008
     * Annex H or closest approximation.
     * Most significant codes:
     * - Any defined in 22.001 F.4 (for generating busy/congestion)
     * - Cause 68: ACM >= ACMMax
     */
    public void getLastCallFailCause (Message result) {
        int[] ret = new int[1];

        ret[0] = nextCallFailCause;
        resultSuccess(result, ret);
    }

    /**
     * @deprecated
     */
    public void getLastPdpFailCause (Message result) {
        unimplemented(result);
    }

    public void getLastDataCallFailCause(Message result) {
        //
        unimplemented(result);
    }

    public void setMute (boolean enableMute, Message result) {unimplemented(result);}

    public void getMute (Message result) {unimplemented(result);}

    /** 
     * response.obj is an AsyncResult
     * response.obj.result is an int[2]
     * response.obj.result[0] is received signal strength (0-31, 99) 
     * response.obj.result[1] is  bit error rate (0-7, 99) 
     * as defined in TS 27.007 8.5
     */
    public void getSignalStrength (Message result) {
        int ret[] = new int[2];

        ret[0] = 23;
        ret[1] = 0;

        resultSuccess(result, ret);
    }

     /**
     * Assign a specified band for RF configuration.
     *
     * @param bandMode one of BM_*_BAND
     * @param result is callback message
     */
    public void setBandMode (int bandMode, Message result) {
        resultSuccess(result, null);
    }

    /**
     * Query the list of band mode supported by RF.
     *
     * @param result is callback message
     *        ((AsyncResult)response.obj).result  is an int[] with every
     *        element representing one avialable BM_*_BAND
     */
    public void queryAvailableBandMode (Message result) {
        int ret[] = new int [4];

        ret[0] = 4;
        ret[1] = Phone.BM_US_BAND;
        ret[2] = Phone.BM_JPN_BAND;
        ret[3] = Phone.BM_AUS_BAND;

        resultSuccess(result, ret);
    }

    /**
     * {@inheritDoc}
     */
    public void sendTerminalResponse(String contents, Message response) {
        resultSuccess(response, null);
    }

    /**
     * {@inheritDoc}
     */
    public void sendEnvelope(String contents, Message response) {
        resultSuccess(response, null);
    }

    /**
     * {@inheritDoc}
     */
    public void handleCallSetupRequestFromSim(
            boolean accept, Message response) {
        resultSuccess(response, null);
    }

    /**
     * response.obj.result is an String[14]
     * See ril.h for details
     *
     * Please note that registration state 4 ("unknown") is treated
     * as "out of service" above
     */
    public void getRegistrationState (Message result) {
        String ret[] = new String[14];

        ret[0] = "5"; // registered roam
        ret[1] = null;
        ret[2] = null;
        ret[3] = null;
        ret[4] = null;
        ret[5] = null;
        ret[6] = null;
        ret[7] = null;
        ret[8] = null;
        ret[9] = null;
        ret[10] = null;
        ret[11] = null;
        ret[12] = null;
        ret[13] = null;
        ret[14] = null;

        resultSuccess(result, ret);
    }

    /**
     * response.obj.result is an String[4]
     * response.obj.result[0] is registration state 0-5 from TS 27.007 7.2
     * response.obj.result[1] is LAC if registered or NULL if not
     * response.obj.result[2] is CID if registered or NULL if not
     * response.obj.result[3] indicates the available radio technology, where:
     *      0 == unknown
     *      1 == GPRS only
     *      2 == EDGE
     *      3 == UMTS
     *
     * valid LAC are 0x0000 - 0xffff
     * valid CID are 0x00000000 - 0xffffffff
     *
     * Please note that registration state 4 ("unknown") is treated
     * as "out of service" in the Android telephony system
     */
    public void getGPRSRegistrationState (Message result) {
        String ret[] = new String[4];

        ret[0] = "5"; // registered roam
        ret[1] = null;
        ret[2] = null;
        ret[3] = "2";

        resultSuccess(result, ret);
    }

    /**
     * response.obj.result is a String[3]
     * response.obj.result[0] is long alpha or null if unregistered
     * response.obj.result[1] is short alpha or null if unregistered
     * response.obj.result[2] is numeric or null if unregistered
     */ 
    public void getOperator(Message result) {
        String[] ret = new String[3];

        ret[0] = "El Telco Loco";
        ret[1] = "Telco Loco";
        ret[2] = "001001";

        resultSuccess(result, ret);
    }

    /**
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */    
    public void sendDtmf(char c, Message result) {
        resultSuccess(result, null);
    }

    /**
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */
    public void startDtmf(char c, Message result) {
        resultSuccess(result, null);
    }

    /**
     *  ar.exception carries exception on failure
     *  ar.userObject contains the orignal value of result.obj
     *  ar.result is null on success and failure
     */
    public void stopDtmf(Message result) {
        resultSuccess(result, null);
    }

    /**
     * smscPDU is smsc address in PDU form GSM BCD format prefixed
     *      by a length byte (as expected by TS 27.005) or NULL for default SMSC
     * pdu is SMS in PDU format as an ASCII hex string
     *      less the SMSC address     
     */
    public void sendSMS (String smscPDU, String pdu, Message result) {unimplemented(result);}

    public void deleteSmsOnSim(int index, Message response) {
        Log.d(LOG_TAG, "Delete message at index " + index);
        unimplemented(response);
    }

    public void deleteSmsOnRuim(int index, Message response) {
        Log.d(LOG_TAG, "Delete RUIM message at index " + index);
        unimplemented(response);
    }

    public void writeSmsToSim(int status, String smsc, String pdu, Message response) {
        Log.d(LOG_TAG, "Write SMS to SIM with status " + status);
        unimplemented(response);
    }

    public void writeSmsToRuim(int status, String pdu, Message response) {
        Log.d(LOG_TAG, "Write SMS to RUIM with status " + status);
        unimplemented(response);
    }

    public void setupDefaultPDP(String apn, String user, String password, Message result) {
        unimplemented(result);
    }

    public void setupDataCall(String radioTechnology, String profile, String apn, String user,
            String password, Message result) {
        unimplemented(result);
    }

    public void deactivateDataCall(int cid, Message result) {unimplemented(result);}

    /**
     * @deprecated
     */
    public void deactivateDefaultPDP(int cid, Message result) {unimplemented(result);}

    public void setPreferredNetworkType(int networkType , Message result) {
        mNetworkType = networkType;
        resultSuccess(result, null);
    }

    public void getPreferredNetworkType(Message result) {
        int ret[] = new int[1];

        ret[0] = mNetworkType;
        resultSuccess(result, ret);
    }

    public void getNeighboringCids(Message result) {
        int ret[] = new int[7];

        ret[0] = 6;
        for (int i = 1; i<7; i++) {
            ret[i] = i;
        }
        resultSuccess(result, ret);
    }

    public void setLocationUpdates(boolean enable, Message response) {
        unimplemented(response);
    }

    private boolean isSimLocked() {
        if (mSimLockedState != SimLockState.NONE) {
            return true;
        }
        return false;
    }

    public void setRadioPower(boolean on, Message result) {
        if(on) {
            if (isSimLocked()) {
                Log.i("SIM", "[SimCmd] setRadioPower: SIM locked! state=" +
                        mSimLockedState);
                setRadioState(RadioState.SIM_LOCKED_OR_ABSENT);
            }
            else {
                setRadioState(RadioState.SIM_READY);
            }
        } else {
            setRadioState(RadioState.RADIO_OFF);
        }
    }


    public void acknowledgeLastIncomingSMS(boolean success, Message result) {
        unimplemented(result);
    }

    public void acknowledgeLastIncomingCdmaSms(boolean success, Message result) {
        unimplemented(result);
    }

    /** 
     * parameters equivilient to 27.007 AT+CRSM command 
     * response.obj will be an AsyncResult
     * response.obj.userObj will be a SimIoResult on success
     */
    public void iccIO (int command, int fileid, String path, int p1, int p2,
                       int p3, String data, String pin2, Message result) {
        unimplemented(result);
    }

    /**
     * (AsyncResult)response.obj).result is an int[] with element [0] set to
     * 1 for "CLIP is provisioned", and 0 for "CLIP is not provisioned". 
     *
     * @param response is callback message
     */
    public void queryCLIP(Message response) { unimplemented(response); }


    /**
     * response.obj will be a an int[2]
     *
     * response.obj[0] will be TS 27.007 +CLIR parameter 'n'
     *  0 presentation indicator is used according to the subscription of the CLIR service 
     *  1 CLIR invocation 
     *  2 CLIR suppression 
     *
     * response.obj[1] will be TS 27.007 +CLIR parameter 'm'
     *  0 CLIR not provisioned 
     *  1 CLIR provisioned in permanent mode 
     *  2 unknown (e.g. no network, etc.) 
     *  3 CLIR temporary mode presentation restricted 
     *  4 CLIR temporary mode presentation allowed 
     */

    public void getCLIR(Message result) {unimplemented(result);}
    
    /**
     * clirMode is one of the CLIR_* constants above
     *
     * response.obj is null
     */
    
    public void setCLIR(int clirMode, Message result) {unimplemented(result);}

    /**
     * (AsyncResult)response.obj).result is an int[] with element [0] set to
     * 0 for disabled, 1 for enabled. 
     *
     * @param serviceClass is a sum of SERVICE_CLASS_*
     * @param response is callback message
     */
    
    public void queryCallWaiting(int serviceClass, Message response) {
        unimplemented(response);
    }
    
    /**
     * @param enable is true to enable, false to disable
     * @param serviceClass is a sum of SERVICE_CLASS_*
     * @param response is callback message
     */
    
    public void setCallWaiting(boolean enable, int serviceClass,
            Message response) {
        unimplemented(response);
    }

    /**
     * @param action is one of CF_ACTION_*
     * @param cfReason is one of CF_REASON_*
     * @param serviceClass is a sum of SERVICE_CLASSS_* 
     */
    public void setCallForward(int action, int cfReason, int serviceClass, 
            String number, int timeSeconds, Message result) {unimplemented(result);}

    /**
     * cfReason is one of CF_REASON_*
     *
     * ((AsyncResult)response.obj).result will be an array of
     * CallForwardInfo's
     * 
     * An array of length 0 means "disabled for all codes"
     */
    public void queryCallForwardStatus(int cfReason, int serviceClass,
            String number, Message result) {unimplemented(result);}

    public void setNetworkSelectionModeAutomatic(Message result) {unimplemented(result);}

    public void setNetworkSelectionModeManual(
            String operatorNumeric, Message result) {unimplemented(result);}

    /**
     * Queries whether the current network selection mode is automatic
     * or manual
     *
     * ((AsyncResult)response.obj).result  is an int[] with element [0] being
     * a 0 for automatic selection and a 1 for manual selection
     */

    public void getNetworkSelectionMode(Message result) {
        int ret[] = new int[1];

        ret[0] = 0;
        resultSuccess(result, ret);
    }

    /**
     * Queries the currently available networks
     *
     * ((AsyncResult)response.obj).result  is a List of NetworkInfo objects
     */
    public void getAvailableNetworks(Message result) {unimplemented(result);}

    public void getBasebandVersion (Message result) {
        resultSuccess(result, "SimulatedCommands"); 
    }

    /**
     * Simulates an incoming USSD message
     * @param statusCode  Status code string. See <code>setOnUSSD</code>
     * in CommandsInterface.java
     * @param message Message text to send or null if none
     */
    public void triggerIncomingUssd(String statusCode, String message) {
        if (mUSSDRegistrant != null) {
            String[] result = {statusCode, message};
            mUSSDRegistrant.notifyResult(result);            
        }
    }


    public void sendUSSD (String ussdString, Message result) {

        // We simulate this particular sequence
        if (ussdString.equals("#646#")) {
            resultSuccess(result, null);

            // 0 == USSD-Notify
            triggerIncomingUssd("0", "You have NNN minutes remaining.");
        } else {
            resultSuccess(result, null);

            triggerIncomingUssd("0", "All Done");
        }
    }

    // inherited javadoc suffices
    public void cancelPendingUssd (Message response) {
        resultSuccess(response, null);
    }


    public void resetRadio(Message result) {
        unimplemented(result);
    }

    public void invokeOemRilRequestRaw(byte[] data, Message response) {
        // Just echo back data
        if (response != null) {
            AsyncResult.forMessage(response).result = data;
            response.sendToTarget();
        }
    }

    public void invokeOemRilRequestStrings(String[] strings, Message response) {
        // Just echo back data
        if (response != null) {
            AsyncResult.forMessage(response).result = strings;
            response.sendToTarget();
        }
    }

    //***** SimulatedRadioControl

    
    /** Start the simulated phone ringing */
    public void
    triggerRing(String number) {
        simulatedCallState.triggerRing(number);
        mCallStateRegistrants.notifyRegistrants();
    }

    public void
    progressConnectingCallState() {
        simulatedCallState.progressConnectingCallState();
        mCallStateRegistrants.notifyRegistrants();
    }

    /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */
    public void
    progressConnectingToActive() {
        simulatedCallState.progressConnectingToActive();
        mCallStateRegistrants.notifyRegistrants();
    }

    /** automatically progress mobile originated calls to ACTIVE.
     *  default to true
     */
    public void
    setAutoProgressConnectingCall(boolean b) {
        simulatedCallState.setAutoProgressConnectingCall(b);
    }

    public void
    setNextDialFailImmediately(boolean b) {
        simulatedCallState.setNextDialFailImmediately(b);
    }

    public void 
    setNextCallFailCause(int gsmCause) {
        nextCallFailCause = gsmCause;    
    }

    public void
    triggerHangupForeground() {
        simulatedCallState.triggerHangupForeground();
        mCallStateRegistrants.notifyRegistrants();
    }

    /** hangup holding calls */
    public void
    triggerHangupBackground() {
        simulatedCallState.triggerHangupBackground();
        mCallStateRegistrants.notifyRegistrants();
    }

    public void triggerSsn(int type, int code) {
        SuppServiceNotification not = new SuppServiceNotification();
        not.notificationType = type;
        not.code = code;
        mSsnRegistrant.notifyRegistrant(new AsyncResult(null, not, null));
    }

    public void
    shutdown() {
        setRadioState(RadioState.RADIO_UNAVAILABLE);
        Looper looper = mHandlerThread.getLooper();
        if (looper != null) {
            looper.quit();
        }
    }

    /** hangup all */

    public void
    triggerHangupAll() {
        simulatedCallState.triggerHangupAll();
        mCallStateRegistrants.notifyRegistrants();
    }

    public void
    triggerIncomingSMS(String message) {
        //TODO
    }

    public void
    pauseResponses() {
        pausedResponseCount++;
    }

    public void
    resumeResponses() {
        pausedResponseCount--;

        if (pausedResponseCount == 0) {
            for (int i = 0, s = pausedResponses.size(); i < s ; i++) {
                pausedResponses.get(i).sendToTarget();
            }
            pausedResponses.clear();
        } else {
            Log.e("GSM", "SimulatedCommands.resumeResponses < 0");
        }
    }

    //***** Private Methods

    private void unimplemented(Message result) {
        if (result != null) {
            AsyncResult.forMessage(result).exception 
                = new RuntimeException("Unimplemented");

            if (pausedResponseCount > 0) {
                pausedResponses.add(result);
            } else {
                result.sendToTarget();
            }
        }
    }

    private void resultSuccess(Message result, Object ret) {
        if (result != null) {
            AsyncResult.forMessage(result).result = ret;
            if (pausedResponseCount > 0) {
                pausedResponses.add(result);
            } else {
                result.sendToTarget();
            }
        }
    }

    private void resultFail(Message result, Throwable tr) {
        if (result != null) {
            AsyncResult.forMessage(result).exception = tr;
            if (pausedResponseCount > 0) {
                pausedResponses.add(result);
            } else {
                result.sendToTarget();
            }
        }
    }

    // ***** Methods for CDMA support
    public void
    getDeviceIdentity(Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    public void 
    getCDMASubscription(Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    public void 
    setCdmaSubscription(int cdmaSubscriptionType, Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    public void queryCdmaRoamingPreference(Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    public void
    setPhoneType(int phoneType) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
    }

    public void getPreferredVoicePrivacy(Message result) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(result);
    }

    public void setPreferredVoicePrivacy(boolean enable, Message result) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(result);
    }

    /**
     *  Set the TTY mode for the CDMA phone
     *
     * @param enable is true to enable, false to disable
     * @param serviceClass is a sum of SERVICE_CLASS_*
     * @param response is callback message
     */
    public void setTTYModeEnabled(boolean enable, Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    /**
     *  Query the TTY mode for the CDMA phone
     * (AsyncResult)response.obj).result is an int[] with element [0] set to
     * 0 for disabled, 1 for enabled. 
     *  
     * @param serviceClass is a sum of SERVICE_CLASS_*
     * @param response is callback message
     */
    public void queryTTYModeEnabled(Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    /**
     * {@inheritDoc}
     */
    public void sendCDMAFeatureCode(String FeatureCode, Message response) {
        Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
        unimplemented(response);
    }

    /**
     * {@inheritDoc}
     */
    public void sendCdmaSms(byte[] pdu, Message response){
       Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
    }

    public void activateCdmaBroadcastSms(int activate, Message result) {
        // TODO Auto-generated method stub

    }

    public void getCdmaBroadcastConfig(Message result) {
        // TODO Auto-generated method stub

    }

    public void setCdmaBroadcastConfig(int[] configValuesArray, Message result) {
        // TODO Auto-generated method stub

    }

}
