/*
 * Copyright (C) 2016 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.provider.Settings;
import android.provider.Telephony;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.LocalLog;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;

/**
 * Carrier Action Agent(CAA) paired with
 * {@link com.android.internal.telephony.CarrierSignalAgent CarrierSignalAgent},
 * serves as an agent to dispatch carrier actions from carrier apps to different telephony modules,
 * {@link android.telephony.TelephonyManager#carrierActionSetRadioEnabled(int, boolean)
 * carrierActionSetRadioEnabled} for example.
 *
 * CAA supports dynamic registration where different telephony modules could listen for a specific
 * carrier action event and implement their own handler. CCA will dispatch the event to all
 * interested parties and maintain the received action states internally for future inspection.
 * Each CarrierActionAgent is associated with a phone object.
 */
public class CarrierActionAgent extends Handler {
    private static final String LOG_TAG = "CarrierActionAgent";
    private static final boolean DBG = true;
    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);

    /** A list of carrier actions */
    public static final int CARRIER_ACTION_SET_METERED_APNS_ENABLED        = 0;
    public static final int CARRIER_ACTION_SET_RADIO_ENABLED               = 1;
    public static final int CARRIER_ACTION_RESET                           = 2;
    public static final int CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS   = 3;
    public static final int EVENT_APM_SETTINGS_CHANGED                     = 4;
    public static final int EVENT_MOBILE_DATA_SETTINGS_CHANGED             = 5;
    public static final int EVENT_DATA_ROAMING_OFF                         = 6;
    public static final int EVENT_SIM_STATE_CHANGED                        = 7;
    public static final int EVENT_APN_SETTINGS_CHANGED                     = 8;

    /** Member variables */
    private final Phone mPhone;
    /** registrant list per carrier action */
    private RegistrantList mMeteredApnEnableRegistrants = new RegistrantList();
    private RegistrantList mRadioEnableRegistrants = new RegistrantList();
    private RegistrantList mDefaultNetworkReportRegistrants = new RegistrantList();
    /** local log for carrier actions */
    private LocalLog mMeteredApnEnabledLog = new LocalLog(8);
    private LocalLog mRadioEnabledLog = new LocalLog(8);
    private LocalLog mReportDefaultNetworkStatusLog = new LocalLog(8);
    /** carrier actions */
    private Boolean mCarrierActionOnMeteredApnEnabled = true;
    private Boolean mCarrierActionOnRadioEnabled = true;
    private Boolean mCarrierActionReportDefaultNetworkStatus = false;
    /** content observer for APM change */
    private final SettingsObserver mSettingsObserver;

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            final String iccState = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
            if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)){
                if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                    // ignore rebroadcast since carrier apps are direct boot aware.
                    return;
                }
                final int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
                        SubscriptionManager.INVALID_PHONE_INDEX);
                if (mPhone.getPhoneId() == phoneId) {
                    sendMessage(obtainMessage(EVENT_SIM_STATE_CHANGED, iccState));
                }
            }
        }
    };

    /** Constructor */
    public CarrierActionAgent(Phone phone) {
        mPhone = phone;
        mPhone.getContext().registerReceiver(mReceiver,
                new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED));
        mSettingsObserver = new SettingsObserver(mPhone.getContext(), this);
        if (DBG) log("Creating CarrierActionAgent");
    }

    @Override
    public void handleMessage(Message msg) {
        // skip notification if the input carrier action is same as the current one.
        Boolean enabled = getCarrierActionEnabled(msg.what);
        if (enabled != null && enabled == (boolean) msg.obj) return;
        switch (msg.what) {
            case CARRIER_ACTION_SET_METERED_APNS_ENABLED:
                mCarrierActionOnMeteredApnEnabled = (boolean) msg.obj;
                log("SET_METERED_APNS_ENABLED: " + mCarrierActionOnMeteredApnEnabled);
                mMeteredApnEnabledLog.log("SET_METERED_APNS_ENABLED: "
                        + mCarrierActionOnMeteredApnEnabled);
                int otaspState = (mCarrierActionOnMeteredApnEnabled)
                        ? mPhone.getServiceStateTracker().getOtasp()
                        : TelephonyManager.OTASP_SIM_UNPROVISIONED;
                mPhone.notifyOtaspChanged(otaspState);
                mMeteredApnEnableRegistrants.notifyRegistrants(
                        new AsyncResult(null, mCarrierActionOnMeteredApnEnabled, null));
                break;
            case CARRIER_ACTION_SET_RADIO_ENABLED:
                mCarrierActionOnRadioEnabled = (boolean) msg.obj;
                log("SET_RADIO_ENABLED: " + mCarrierActionOnRadioEnabled);
                mRadioEnabledLog.log("SET_RADIO_ENABLED: " + mCarrierActionOnRadioEnabled);
                mRadioEnableRegistrants.notifyRegistrants(
                        new AsyncResult(null, mCarrierActionOnRadioEnabled, null));
                break;
            case CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS:
                mCarrierActionReportDefaultNetworkStatus = (boolean) msg.obj;
                log("CARRIER_ACTION_REPORT_AT_DEFAULT_NETWORK_STATUS: "
                        + mCarrierActionReportDefaultNetworkStatus);
                mReportDefaultNetworkStatusLog.log("REGISTER_DEFAULT_NETWORK_STATUS: "
                        + mCarrierActionReportDefaultNetworkStatus);
                mDefaultNetworkReportRegistrants.notifyRegistrants(
                        new AsyncResult(null, mCarrierActionReportDefaultNetworkStatus, null));
                break;
            case CARRIER_ACTION_RESET:
                log("CARRIER_ACTION_RESET");
                carrierActionReset();
                break;
            case EVENT_APM_SETTINGS_CHANGED:
                log("EVENT_APM_SETTINGS_CHANGED");
                if ((Settings.Global.getInt(mPhone.getContext().getContentResolver(),
                        Settings.Global.AIRPLANE_MODE_ON, 0) != 0)) {
                    carrierActionReset();
                }
                break;
            case EVENT_MOBILE_DATA_SETTINGS_CHANGED:
                log("EVENT_MOBILE_DATA_SETTINGS_CHANGED");
                if (!mPhone.isUserDataEnabled()) carrierActionReset();
                break;
            case EVENT_DATA_ROAMING_OFF:
                log("EVENT_DATA_ROAMING_OFF");
                // reset carrier actions when exit roaming state.
                carrierActionReset();
                break;
            case EVENT_SIM_STATE_CHANGED:
                String iccState = (String) msg.obj;
                if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(iccState)) {
                    log("EVENT_SIM_STATE_CHANGED status: " + iccState);
                    carrierActionReset();
                    String mobileData = Settings.Global.MOBILE_DATA;
                    if (TelephonyManager.getDefault().getSimCount() != 1) {
                        mobileData = mobileData + mPhone.getSubId();
                    }
                    mSettingsObserver.observe(Settings.Global.getUriFor(mobileData),
                            EVENT_MOBILE_DATA_SETTINGS_CHANGED);
                    mSettingsObserver.observe(
                            Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
                            EVENT_APM_SETTINGS_CHANGED);
                    mSettingsObserver.observe(
                            Telephony.Carriers.CONTENT_URI, EVENT_APN_SETTINGS_CHANGED);
                    if (mPhone.getServiceStateTracker() != null) {
                        mPhone.getServiceStateTracker().registerForDataRoamingOff(
                                this, EVENT_DATA_ROAMING_OFF, null, false);
                    }
                } else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(iccState)) {
                    log("EVENT_SIM_STATE_CHANGED status: " + iccState);
                    carrierActionReset();
                    mSettingsObserver.unobserve();
                    if (mPhone.getServiceStateTracker() != null) {
                        mPhone.getServiceStateTracker().unregisterForDataRoamingOff(this);
                    }
                }
                break;
            case EVENT_APN_SETTINGS_CHANGED:
                log("EVENT_APN_SETTINGS_CHANGED");
                // Reset carrier actions when APN change.
                carrierActionReset();
                break;
            default:
                loge("Unknown carrier action: " + msg.what);
        }
    }

    /**
     * Action set from carrier app to enable/disable radio
     */
    public void carrierActionSetRadioEnabled(boolean enabled) {
        sendMessage(obtainMessage(CARRIER_ACTION_SET_RADIO_ENABLED, enabled));
    }

    /**
     * Action set from carrier app to enable/disable metered APNs
     */
    public void carrierActionSetMeteredApnsEnabled(boolean enabled) {
        sendMessage(obtainMessage(CARRIER_ACTION_SET_METERED_APNS_ENABLED, enabled));
    }

    /**
     * Action set from carrier app to start/stop reporting default network status.
     */
    public void carrierActionReportDefaultNetworkStatus(boolean report) {
        sendMessage(obtainMessage(CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS, report));
    }

    public void carrierActionReset() {
        carrierActionReportDefaultNetworkStatus(false);
        carrierActionSetMeteredApnsEnabled(true);
        carrierActionSetRadioEnabled(true);
        // notify configured carrier apps for reset
        mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(
                new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_RESET));
    }

    private RegistrantList getRegistrantsFromAction(int action) {
        switch (action) {
            case CARRIER_ACTION_SET_METERED_APNS_ENABLED:
                return mMeteredApnEnableRegistrants;
            case CARRIER_ACTION_SET_RADIO_ENABLED:
                return mRadioEnableRegistrants;
            case CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS:
                return mDefaultNetworkReportRegistrants;
            default:
                loge("Unsupported action: " + action);
                return null;
        }
    }

    private Boolean getCarrierActionEnabled(int action) {
        switch (action) {
            case CARRIER_ACTION_SET_METERED_APNS_ENABLED:
                return mCarrierActionOnMeteredApnEnabled;
            case CARRIER_ACTION_SET_RADIO_ENABLED:
                return mCarrierActionOnRadioEnabled;
            case CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS:
                return mCarrierActionReportDefaultNetworkStatus;
            default:
                loge("Unsupported action: " + action);
                return null;
        }
    }

    /**
     * Register with CAA for a specific event.
     * @param action which carrier action registrant is interested in
     * @param notifyNow if carrier action has once set, notify registrant right after
     *                  registering, so that registrants will get the latest carrier action.
     */
    public void registerForCarrierAction(int action, Handler h, int what, Object obj,
                                         boolean notifyNow) {
        Boolean carrierAction = getCarrierActionEnabled(action);
        if (carrierAction == null) {
            throw new IllegalArgumentException("invalid carrier action: " + action);
        }
        RegistrantList list = getRegistrantsFromAction(action);
        Registrant r = new Registrant(h, what, obj);
        list.add(r);
        if (notifyNow) {
            r.notifyRegistrant(new AsyncResult(null, carrierAction, null));
        }
    }

    /**
     * Unregister with CAA for a specific event. Callers will no longer be notified upon such event.
     * @param action which carrier action caller is no longer interested in
     */
    public void unregisterForCarrierAction(Handler h, int action) {
        RegistrantList list = getRegistrantsFromAction(action);
        if (list == null) {
            throw new IllegalArgumentException("invalid carrier action: " + action);
        }
        list.remove(h);
    }

    @VisibleForTesting
    public ContentObserver getContentObserver() {
        return mSettingsObserver;
    }

    private void log(String s) {
        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
    }

    private void loge(String s) {
        Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
    }

    private void logv(String s) {
        Rlog.v(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
        pw.println(" mCarrierActionOnMeteredApnsEnabled Log:");
        ipw.increaseIndent();
        mMeteredApnEnabledLog.dump(fd, ipw, args);
        ipw.decreaseIndent();

        pw.println(" mCarrierActionOnRadioEnabled Log:");
        ipw.increaseIndent();
        mRadioEnabledLog.dump(fd, ipw, args);
        ipw.decreaseIndent();

        pw.println(" mCarrierActionReportDefaultNetworkStatus Log:");
        ipw.increaseIndent();
        mReportDefaultNetworkStatusLog.dump(fd, ipw, args);
        ipw.decreaseIndent();
    }
}
