/*
 * Copyright (C) 2021 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;

import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
import static android.telephony.TelephonyManager.HAL_SERVICE_IMS;
import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING;
import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM;
import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
import static android.telephony.TelephonyManager.HAL_SERVICE_SIM;
import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.UserHandle;

import com.android.telephony.Rlog;

/** This class provides wrapper APIs for binding interfaces to mock service. */
public class MockModem {
    private static final String TAG = "MockModem";
    private static final String BIND_IRADIOMODEM = "android.telephony.mockmodem.iradiomodem";
    private static final String BIND_IRADIOSIM = "android.telephony.mockmodem.iradiosim";
    private static final String BIND_IRADIOMESSAGING =
            "android.telephony.mockmodem.iradiomessaging";
    private static final String BIND_IRADIODATA = "android.telephony.mockmodem.iradiodata";
    private static final String BIND_IRADIONETWORK = "android.telephony.mockmodem.iradionetwork";
    private static final String BIND_IRADIOVOICE = "android.telephony.mockmodem.iradiovoice";
    private static final String BIND_IRADIOIMS = "android.telephony.mockmodem.iradioims";
    private static final String BIND_IRADIOCONFIG = "android.telephony.mockmodem.iradioconfig";
    private static final String PHONE_ID = "phone_id";

    private static final byte DEFAULT_PHONE_ID = 0x00;

    static final int RADIOCONFIG_SERVICE = RIL.MAX_SERVICE_IDX + 1;

    static final int BINDER_RETRY_MILLIS = 3 * 100;
    static final int BINDER_MAX_RETRY = 10;

    private Context mContext;
    private String mServiceName;
    private String mPackageName;

    private IBinder mModemBinder;
    private IBinder mSimBinder;
    private IBinder mMessagingBinder;
    private IBinder mDataBinder;
    private IBinder mNetworkBinder;
    private IBinder mVoiceBinder;
    private IBinder mImsBinder;
    private IBinder mConfigBinder;
    private ServiceConnection mModemServiceConnection;
    private ServiceConnection mSimServiceConnection;
    private ServiceConnection mMessagingServiceConnection;
    private ServiceConnection mDataServiceConnection;
    private ServiceConnection mNetworkServiceConnection;
    private ServiceConnection mVoiceServiceConnection;
    private ServiceConnection mImsServiceConnection;
    private ServiceConnection mConfigServiceConnection;

    private byte mPhoneId;
    private String mTag;

    MockModem(Context context, String serviceName) {
        this(context, serviceName, 0);
    }

    MockModem(Context context, String serviceName, int phoneId) {
        mPhoneId = (byte) phoneId;
        mTag = TAG + "-" + mPhoneId;
        mContext = context;
        String[] componentInfo = serviceName.split("/", 2);
        mPackageName = componentInfo[0];
        mServiceName = componentInfo[1];
    }

    private class MockModemConnection implements ServiceConnection {
        private int mService;

        MockModemConnection(int module) {
            mService = module;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Rlog.d(mTag, "IRadio " + getModuleName(mService) + "  - onServiceConnected");

            if (mService == HAL_SERVICE_MODEM) {
                mModemBinder = binder;
            } else if (mService == HAL_SERVICE_SIM) {
                mSimBinder = binder;
            } else if (mService == HAL_SERVICE_MESSAGING) {
                mMessagingBinder = binder;
            } else if (mService == HAL_SERVICE_DATA) {
                mDataBinder = binder;
            } else if (mService == HAL_SERVICE_NETWORK) {
                mNetworkBinder = binder;
            } else if (mService == HAL_SERVICE_VOICE) {
                mVoiceBinder = binder;
            } else if (mService == HAL_SERVICE_IMS) {
                mImsBinder = binder;
            } else if (mService == RADIOCONFIG_SERVICE) {
                mConfigBinder = binder;
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Rlog.d(mTag, "IRadio " + getModuleName(mService) + "  - onServiceDisconnected");

            if (mService == HAL_SERVICE_MODEM) {
                mModemBinder = null;
            } else if (mService == HAL_SERVICE_SIM) {
                mSimBinder = null;
            } else if (mService == HAL_SERVICE_MESSAGING) {
                mMessagingBinder = null;
            } else if (mService == HAL_SERVICE_DATA) {
                mDataBinder = null;
            } else if (mService == HAL_SERVICE_NETWORK) {
                mNetworkBinder = null;
            } else if (mService == HAL_SERVICE_VOICE) {
                mVoiceBinder = null;
            } else if (mService == HAL_SERVICE_IMS) {
                mImsBinder = null;
            } else if (mService == RADIOCONFIG_SERVICE) {
                mConfigBinder = null;
            }
        }
    }

    private boolean bindModuleToMockModemService(
            String actionName, ServiceConnection serviceConnection) {
        return bindModuleToMockModemService(DEFAULT_PHONE_ID, actionName, serviceConnection);
    }

    private boolean bindModuleToMockModemService(
            byte phoneId, String actionName, ServiceConnection serviceConnection) {
        boolean status = false;

        Intent intent = new Intent();
        intent.setComponent(new ComponentName(mPackageName, mServiceName));
        intent.setAction(actionName + phoneId);
        intent.putExtra(PHONE_ID, phoneId);

        status = mContext.bindServiceAsUser(intent, serviceConnection, Context.BIND_AUTO_CREATE,
                UserHandle.of(ActivityManager.getCurrentUser()));
        return status;
    }

    /** waitForBinder */
    public IBinder getServiceBinder(int service) {
        switch (service) {
            case HAL_SERVICE_MODEM:
                return mModemBinder;
            case HAL_SERVICE_SIM:
                return mSimBinder;
            case HAL_SERVICE_MESSAGING:
                return mMessagingBinder;
            case HAL_SERVICE_DATA:
                return mDataBinder;
            case HAL_SERVICE_NETWORK:
                return mNetworkBinder;
            case HAL_SERVICE_VOICE:
                return mVoiceBinder;
            case HAL_SERVICE_IMS:
                return mImsBinder;
            case RADIOCONFIG_SERVICE:
                return mConfigBinder;
            default:
                return null;
        }
    }

    /** Binding interfaces with mock modem service */
    public void bindAllMockModemService() {
        for (int service = RIL.MIN_SERVICE_IDX; service <= RIL.MAX_SERVICE_IDX; service++) {
            bindToMockModemService(service);
        }
    }

    /** bindToMockModemService */
    public void bindToMockModemService(int service) {
        if (service == RADIOCONFIG_SERVICE) {
            if (mConfigBinder == null) {
                mConfigServiceConnection = new MockModemConnection(RADIOCONFIG_SERVICE);

                boolean status =
                        bindModuleToMockModemService(BIND_IRADIOCONFIG, mConfigServiceConnection);
                if (!status) {
                    Rlog.d(mTag, "IRadio Config bind fail");
                    mConfigServiceConnection = null;
                }
            } else {
                Rlog.d(mTag, "IRadio Config is bound");
            }
        } else if (service == HAL_SERVICE_MODEM) {
            if (mModemBinder == null) {
                mModemServiceConnection = new MockModemConnection(HAL_SERVICE_MODEM);

                boolean status =
                        bindModuleToMockModemService(
                                mPhoneId, BIND_IRADIOMODEM, mModemServiceConnection);
                if (!status) {
                    Rlog.d(mTag, "IRadio Modem bind fail");
                    mModemServiceConnection = null;
                }
            } else {
                Rlog.d(mTag, "IRadio Modem is bound");
            }
        } else if (service == HAL_SERVICE_SIM) {
            if (mSimBinder == null) {
                mSimServiceConnection = new MockModemConnection(HAL_SERVICE_SIM);

                boolean status =
                        bindModuleToMockModemService(
                                mPhoneId, BIND_IRADIOSIM, mSimServiceConnection);
                if (!status) {
                    Rlog.d(mTag, "IRadio Sim bind fail");
                    mSimServiceConnection = null;
                }
            } else {
                Rlog.d(mTag, "IRadio Sim is bound");
            }
        } else if (service == HAL_SERVICE_MESSAGING) {
            if (mMessagingBinder == null) {
                mMessagingServiceConnection = new MockModemConnection(HAL_SERVICE_MESSAGING);

                boolean status =
                        bindModuleToMockModemService(
                                mPhoneId, BIND_IRADIOMESSAGING, mMessagingServiceConnection);
                if (!status) {
                    Rlog.d(mTag, "IRadio Messaging bind fail");
                    mMessagingServiceConnection = null;
                }
            } else {
                Rlog.d(mTag, "IRadio Messaging is bound");
            }
        } else if (service == HAL_SERVICE_DATA) {
            if (mDataBinder == null) {
                mDataServiceConnection = new MockModemConnection(HAL_SERVICE_DATA);

                boolean status =
                        bindModuleToMockModemService(
                                mPhoneId, BIND_IRADIODATA, mDataServiceConnection);
                if (!status) {
                    Rlog.d(mTag, "IRadio Data bind fail");
                    mDataServiceConnection = null;
                }
            } else {
                Rlog.d(mTag, "IRadio Data is bound");
            }
        } else if (service == HAL_SERVICE_NETWORK) {
            if (mNetworkBinder == null) {
                mNetworkServiceConnection = new MockModemConnection(HAL_SERVICE_NETWORK);

                boolean status =
                        bindModuleToMockModemService(
                                mPhoneId, BIND_IRADIONETWORK, mNetworkServiceConnection);
                if (!status) {
                    Rlog.d(mTag, "IRadio Network bind fail");
                    mNetworkServiceConnection = null;
                }
            } else {
                Rlog.d(mTag, "IRadio Network is bound");
            }
        } else if (service == HAL_SERVICE_VOICE) {
            if (mVoiceBinder == null) {
                mVoiceServiceConnection = new MockModemConnection(HAL_SERVICE_VOICE);

                boolean status =
                        bindModuleToMockModemService(
                                mPhoneId, BIND_IRADIOVOICE, mVoiceServiceConnection);
                if (!status) {
                    Rlog.d(mTag, "IRadio Voice bind fail");
                    mVoiceServiceConnection = null;
                }
            } else {
                Rlog.d(mTag, "IRadio Voice is bound");
            }
        } else if (service == HAL_SERVICE_IMS) {
            if (mImsBinder == null) {
                mImsServiceConnection = new MockModemConnection(HAL_SERVICE_IMS);

                boolean status =
                        bindModuleToMockModemService(
                                mPhoneId, BIND_IRADIOIMS, mImsServiceConnection);
                if (!status) {
                    Rlog.d(TAG, "IRadio Ims bind fail");
                    mImsServiceConnection = null;
                }
            } else {
                Rlog.d(TAG, "IRadio Ims is bound");
            }
        }
    }

    /** unbindMockModemService */
    public void unbindMockModemService(int service) {

        if (service == RADIOCONFIG_SERVICE) {
            if (mConfigServiceConnection != null) {
                mContext.unbindService(mConfigServiceConnection);
                mConfigServiceConnection = null;
                mConfigBinder = null;
                Rlog.d(mTag, "unbind IRadio Config");
            }
        } else if (service == HAL_SERVICE_MODEM) {
            if (mModemServiceConnection != null) {
                mContext.unbindService(mModemServiceConnection);
                mModemServiceConnection = null;
                mModemBinder = null;
                Rlog.d(mTag, "unbind IRadio Modem");
            }
        } else if (service == HAL_SERVICE_SIM) {
            if (mSimServiceConnection != null) {
                mContext.unbindService(mSimServiceConnection);
                mSimServiceConnection = null;
                mSimBinder = null;
                Rlog.d(mTag, "unbind IRadio Sim");
            }
        } else if (service == HAL_SERVICE_MESSAGING) {
            if (mMessagingServiceConnection != null) {
                mContext.unbindService(mMessagingServiceConnection);
                mMessagingServiceConnection = null;
                mMessagingBinder = null;
                Rlog.d(mTag, "unbind IRadio Messaging");
            }
        } else if (service == HAL_SERVICE_DATA) {
            if (mDataServiceConnection != null) {
                mContext.unbindService(mDataServiceConnection);
                mDataServiceConnection = null;
                mDataBinder = null;
                Rlog.d(mTag, "unbind IRadio Data");
            }
        } else if (service == HAL_SERVICE_NETWORK) {
            if (mNetworkServiceConnection != null) {
                mContext.unbindService(mNetworkServiceConnection);
                mNetworkServiceConnection = null;
                mNetworkBinder = null;
                Rlog.d(mTag, "unbind IRadio Network");
            }
        } else if (service == HAL_SERVICE_VOICE) {
            if (mVoiceServiceConnection != null) {
                mContext.unbindService(mVoiceServiceConnection);
                mVoiceServiceConnection = null;
                mVoiceBinder = null;
                Rlog.d(mTag, "unbind IRadio Voice");
            }
        } else if (service == HAL_SERVICE_IMS) {
            if (mImsServiceConnection != null) {
                mContext.unbindService(mImsServiceConnection);
                mImsServiceConnection = null;
                mImsBinder = null;
                Rlog.d(TAG, "unbind IRadio Ims");
            }
        }
    }

    public String getServiceName() {
        return mServiceName;
    }

    private String getModuleName(int service) {
        switch (service) {
            case HAL_SERVICE_MODEM:
                return "modem";
            case HAL_SERVICE_SIM:
                return "sim";
            case HAL_SERVICE_MESSAGING:
                return "messaging";
            case HAL_SERVICE_DATA:
                return "data";
            case HAL_SERVICE_NETWORK:
                return "network";
            case HAL_SERVICE_VOICE:
                return "voice";
            case HAL_SERVICE_IMS:
                return "ims";
            case RADIOCONFIG_SERVICE:
                return "config";
            default:
                return "none";
        }
    }
}
