/*
 * Copyright (C) 2014 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 android.service.carrier;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;

/**
 * A service that receives calls from the system when new SMS and MMS are
 * sent or received.
 * <p>To extend this class, you must declare the service in your manifest file with
 * the {@link android.Manifest.permission#BIND_CARRIER_SERVICES} permission
 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
 * <pre>
 * &lt;service android:name=".MyMessagingService"
 *          android:label="&#64;string/service_name"
 *          android:permission="android.permission.BIND_CARRIER_SERVICES">
 *     &lt;intent-filter>
 *         &lt;action android:name="android.service.carrier.CarrierMessagingService" />
 *     &lt;/intent-filter>
 * &lt;/service></pre>
 */
public abstract class CarrierMessagingService extends Service {
    /**
     * The {@link android.content.Intent} that must be declared as handled by the service.
     */
    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_INTERFACE
            = "android.service.carrier.CarrierMessagingService";

    /**
     * The default bitmask value passed to the callback of {@link #onReceiveTextSms} with all
     * {@code RECEIVE_OPTIONS_x} flags cleared to indicate that the message should be kept and a
     * new message notification should be shown.
     *
     * @see #RECEIVE_OPTIONS_DROP
     * @see #RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE
     */
    public static final int RECEIVE_OPTIONS_DEFAULT = 0;

    /**
     * Used to set the flag in the bitmask passed to the callback of {@link #onReceiveTextSms} to
     * indicate that the inbound SMS should be dropped.
     */
    public static final int RECEIVE_OPTIONS_DROP = 0x1;

    /**
     * Used to set the flag in the bitmask passed to the callback of {@link #onReceiveTextSms} to
     * indicate that a new message notification should not be shown to the user when the
     * credential-encrypted storage of the device is not available before the user unlocks the
     * phone. It is only applicable to devices that support file-based encryption.
     */
    public static final int RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE = 0x2;

    /** @hide */
    @IntDef(flag = true, prefix = { "RECEIVE_OPTIONS_" }, value = {
            RECEIVE_OPTIONS_DEFAULT,
            RECEIVE_OPTIONS_DROP,
            RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface FilterCompleteResult{}

    /**
     * Indicates that an SMS or MMS message was successfully sent.
     */
    public static final int SEND_STATUS_OK = 0;

    /**
     * SMS/MMS sending failed. We should retry via the carrier network.
     */
    public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1;

    /**
     * SMS/MMS sending failed. We should not retry via the carrier network.
     */
    public static final int SEND_STATUS_ERROR = 2;

    /** @hide */
    @IntDef(prefix = { "SEND_STATUS_" }, value = {
            SEND_STATUS_OK,
            SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
            SEND_STATUS_ERROR
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface SendResult {}

    /**
     * Successfully downloaded an MMS message.
     */
    public static final int DOWNLOAD_STATUS_OK = 0;

    /**
     * MMS downloading failed. We should retry via the carrier network.
     */
    public static final int DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK = 1;

    /**
     * MMS downloading failed. We should not retry via the carrier network.
     */
    public static final int DOWNLOAD_STATUS_ERROR = 2;

    /** @hide */
    @IntDef(prefix = { "DOWNLOAD_STATUS_" }, value = {
            DOWNLOAD_STATUS_OK,
            DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK,
            DOWNLOAD_STATUS_ERROR
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DownloadResult {}

    /**
     * Flag to request SMS delivery status report.
     */
    public static final int SEND_FLAG_REQUEST_DELIVERY_STATUS = 0x1;

    /** @hide */
    @IntDef(flag = true, prefix = { "SEND_FLAG_" }, value = {
            SEND_FLAG_REQUEST_DELIVERY_STATUS
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface SendRequest {}

    private final ICarrierMessagingWrapper mWrapper = new ICarrierMessagingWrapper();

    /**
     * Override this method to filter inbound SMS messages.
     *
     * @param pdu the PDUs of the message
     * @param format the format of the PDUs, typically "3gpp" or "3gpp2"
     * @param destPort the destination port of a binary SMS, this will be -1 for text SMS
     * @param subId SMS subscription ID of the SIM
     * @param callback result callback. Call with {@code true} to keep an inbound SMS message and
     *        deliver to SMS apps, and {@code false} to drop the message.
     * @deprecated Use {@link #onReceiveTextSms} instead.
     */
    @Deprecated
    public void onFilterSms(@NonNull MessagePdu pdu, @NonNull String format, int destPort,
            int subId, @NonNull ResultCallback<Boolean> callback) {
        // optional
        try {
            callback.onReceiveResult(true);
        } catch (RemoteException ex) {
        }
    }

    /**
     * Override this method to filter inbound SMS messages.
     *
     * <p>This method will be called once for every incoming text SMS. You can invoke the callback
     * with a bitmask to tell the platform how to handle the SMS. For a SMS received on a
     * file-based encryption capable device while the credential-encrypted storage is not available,
     * this method will be called for the second time when the credential-encrypted storage becomes
     * available after the user unlocks the phone, if the bit {@link #RECEIVE_OPTIONS_DROP} is not
     * set when invoking the callback.
     *
     * @param pdu the PDUs of the message
     * @param format the format of the PDUs, typically "3gpp" or "3gpp2"
     * @param destPort the destination port of a binary SMS, this will be -1 for text SMS
     * @param subId SMS subscription ID of the SIM
     * @param callback result callback. Call with a bitmask integer to indicate how the incoming
     *        text SMS should be handled by the platform. Use {@link #RECEIVE_OPTIONS_DROP} and
     *        {@link #RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE}
     *        to set the flags in the bitmask.
     */
    public void onReceiveTextSms(@NonNull MessagePdu pdu, @NonNull String format,
            int destPort, int subId, @NonNull final ResultCallback<Integer> callback) {
        onFilterSms(pdu, format, destPort, subId, new ResultCallback<Boolean>() {
            @Override
            public void onReceiveResult(Boolean result) throws RemoteException {
                callback.onReceiveResult(result ? RECEIVE_OPTIONS_DEFAULT : RECEIVE_OPTIONS_DROP
                    | RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE);
            }
        });
    }

    /**
     * Override this method to intercept text SMSs sent from the device.
     * @deprecated Override {@link #onSendTextSms} below instead.
     *
     * @param text the text to send
     * @param subId SMS subscription ID of the SIM
     * @param destAddress phone number of the recipient of the message
     * @param callback result callback. Call with a {@link SendSmsResult}.
     */
    @Deprecated
    public void onSendTextSms(
            @NonNull String text, int subId, @NonNull String destAddress,
            @NonNull ResultCallback<SendSmsResult> callback) {
        // optional
        try {
            callback.onReceiveResult(new SendSmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 0));
        } catch (RemoteException ex) {
        }
    }

    /**
     * Override this method to intercept text SMSs sent from the device.
     *
     * @param text the text to send
     * @param subId SMS subscription ID of the SIM
     * @param destAddress phone number of the recipient of the message
     * @param sendSmsFlag Flag for sending SMS. Acceptable values are 0 and
     *        {@link #SEND_FLAG_REQUEST_DELIVERY_STATUS}.
     * @param callback result callback. Call with a {@link SendSmsResult}.
     */
    public void onSendTextSms(
            @NonNull String text, int subId, @NonNull String destAddress,
            int sendSmsFlag, @NonNull ResultCallback<SendSmsResult> callback) {
        // optional
        onSendTextSms(text, subId, destAddress, callback);
    }

    /**
     * Override this method to intercept binary SMSs sent from the device.
     * @deprecated Override {@link #onSendDataSms} below instead.
     *
     * @param data the binary content
     * @param subId SMS subscription ID of the SIM
     * @param destAddress phone number of the recipient of the message
     * @param destPort the destination port
     * @param callback result callback. Call with a {@link SendSmsResult}.
     */
    @Deprecated
    public void onSendDataSms(@NonNull byte[] data, int subId,
            @NonNull String destAddress, int destPort,
            @NonNull ResultCallback<SendSmsResult> callback) {
        // optional
        try {
            callback.onReceiveResult(new SendSmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 0));
        } catch (RemoteException ex) {
        }
    }

    /**
     * Override this method to intercept binary SMSs sent from the device.
     *
     * @param data the binary content
     * @param subId SMS subscription ID of the SIM
     * @param destAddress phone number of the recipient of the message
     * @param destPort the destination port
     * @param sendSmsFlag Flag for sending SMS. Acceptable values are 0 and
     *        {@link #SEND_FLAG_REQUEST_DELIVERY_STATUS}.
     * @param callback result callback. Call with a {@link SendSmsResult}.
     */
    public void onSendDataSms(@NonNull byte[] data, int subId,
            @NonNull String destAddress, int destPort, int sendSmsFlag,
            @NonNull ResultCallback<SendSmsResult> callback) {
        // optional
        onSendDataSms(data, subId, destAddress, destPort, callback);
    }

    /**
     * Override this method to intercept long SMSs sent from the device.
     * @deprecated Override {@link #onSendMultipartTextSms} below instead.
     *
     * @param parts a {@link List} of the message parts
     * @param subId SMS subscription ID of the SIM
     * @param destAddress phone number of the recipient of the message
     * @param callback result callback. Call with a {@link SendMultipartSmsResult}.
     */
    @Deprecated
    public void onSendMultipartTextSms(@NonNull List<String> parts,
            int subId, @NonNull String destAddress,
            @NonNull ResultCallback<SendMultipartSmsResult> callback) {
        // optional
        try {
            callback.onReceiveResult(
                    new SendMultipartSmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null));
        } catch (RemoteException ex) {
        }
    }

    /**
     * Override this method to intercept long SMSs sent from the device.
     *
     * @param parts a {@link List} of the message parts
     * @param subId SMS subscription ID of the SIM
     * @param destAddress phone number of the recipient of the message
     * @param sendSmsFlag Flag for sending SMS. Acceptable values are 0 and
     *        {@link #SEND_FLAG_REQUEST_DELIVERY_STATUS}.
     * @param callback result callback. Call with a {@link SendMultipartSmsResult}.
     */
    public void onSendMultipartTextSms(@NonNull List<String> parts,
            int subId, @NonNull String destAddress, int sendSmsFlag,
            @NonNull ResultCallback<SendMultipartSmsResult> callback) {
        // optional
        onSendMultipartTextSms(parts, subId, destAddress, callback);
    }

    /**
     * Override this method to intercept MMSs sent from the device.
     *
     * @param pduUri the content provider URI of the PDU to send
     * @param subId SMS subscription ID of the SIM
     * @param location the optional URI to send this MMS PDU. If this is {code null},
     *        the PDU should be sent to the default MMSC URL.
     * @param callback result callback. Call with a {@link SendMmsResult}.
     */
    public void onSendMms(@NonNull Uri pduUri, int subId,
            @Nullable Uri location, @NonNull ResultCallback<SendMmsResult> callback) {
        // optional
        try {
            callback.onReceiveResult(new SendMmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null));
        } catch (RemoteException ex) {
        }
    }

    /**
     * Override this method to download MMSs received.
     *
     * @param contentUri the content provider URI of the PDU to be downloaded.
     * @param subId SMS subscription ID of the SIM
     * @param location the URI of the message to be downloaded.
     * @param callback result callback. Call with a status code which is one of
     *        {@link #DOWNLOAD_STATUS_OK},
     *        {@link #DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK}, or {@link #DOWNLOAD_STATUS_ERROR}.
     */
    public void onDownloadMms(@NonNull Uri contentUri, int subId, @NonNull Uri location,
            @NonNull ResultCallback<Integer> callback) {
        // optional
        try {
            callback.onReceiveResult(DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
        } catch (RemoteException ex) {
        }
    }

    @Override
    public @Nullable IBinder onBind(@NonNull Intent intent) {
        if (!SERVICE_INTERFACE.equals(intent.getAction())) {
            return null;
        }
        return mWrapper;
    }

    /**
     * The result of sending an MMS.
     */
    public static final class SendMmsResult {
        private int mSendStatus;
        private byte[] mSendConfPdu;

        /**
         * Constructs a SendMmsResult with the MMS send result, and the SendConf PDU.
         *
         * @param sendStatus send status, one of {@link #SEND_STATUS_OK},
         *        {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and
         *        {@link #SEND_STATUS_ERROR}
         * @param sendConfPdu a possibly {code null} SendConf PDU, which confirms that the message
         *        was sent. sendConfPdu is ignored if the {@code result} is not
         *        {@link #SEND_STATUS_OK}.
         */
        public SendMmsResult(int sendStatus, @Nullable byte[] sendConfPdu) {
            mSendStatus = sendStatus;
            mSendConfPdu = sendConfPdu;
        }

        /**
         * Returns the send status of the just-sent MMS.
         *
         * @return the send status which is one of {@link #SEND_STATUS_OK},
         *         {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and {@link #SEND_STATUS_ERROR}
         */
        public int getSendStatus() {
            return mSendStatus;
        }

        /**
         * Returns the SendConf PDU, which confirms that the message was sent.
         *
         * @return the SendConf PDU
         */
        public @Nullable byte[] getSendConfPdu() {
            return mSendConfPdu;
        }
    }

    /**
     * The result of sending an SMS.
     */
    public static final class SendSmsResult {
        private final int mSendStatus;
        private final int mMessageRef;

        /**
         * Constructs a SendSmsResult with the send status and message reference for the
         * just-sent SMS.
         *
         * @param sendStatus send status, one of {@link #SEND_STATUS_OK},
         *        {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and {@link #SEND_STATUS_ERROR}.
         * @param messageRef message reference of the just-sent SMS. This field is applicable only
         *        if send status is {@link #SEND_STATUS_OK}.
         */
        public SendSmsResult(int sendStatus, int messageRef) {
            mSendStatus = sendStatus;
            mMessageRef = messageRef;
        }

        /**
         * Returns the message reference of the just-sent SMS.
         *
         * @return the message reference
         */
        public int getMessageRef() {
            return mMessageRef;
        }

        /**
         * Returns the send status of the just-sent SMS.
         *
         * @return the send status
         */
        public int getSendStatus() {
            return mSendStatus;
        }
    }

    /**
     * The result of sending a multipart SMS.
     */
    public static final class SendMultipartSmsResult {
        private final int mSendStatus;
        private final int[] mMessageRefs;

        /**
         * Constructs a SendMultipartSmsResult with the send status and message references for the
         * just-sent multipart SMS.
         *
         * @param sendStatus send status, one of {@link #SEND_STATUS_OK},
         *        {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and {@link #SEND_STATUS_ERROR}.
         * @param messageRefs an array of message references, one for each part of the
         *        multipart SMS. This field is applicable only if send status is
         *        {@link #SEND_STATUS_OK}.
         */
        public SendMultipartSmsResult(int sendStatus, @Nullable int[] messageRefs) {
            mSendStatus = sendStatus;
            mMessageRefs = messageRefs;
        }

        /**
         * Returns the message references of the just-sent multipart SMS.
         *
         * @return the message references, one for each part of the multipart SMS
         */
        public @Nullable int[] getMessageRefs() {
            return mMessageRefs;
        }

        /**
         * Returns the send status of the just-sent SMS.
         *
         * @return the send status
         */
        public int getSendStatus() {
            return mSendStatus;
        }
    }

    /**
     * A callback interface used to provide results asynchronously.
     */
    public interface ResultCallback<T> {
        /**
         * Invoked when the result is available.
         *
         * @param result the result
         */
        public void onReceiveResult(@NonNull T result) throws RemoteException;
    };

    /**
     * A wrapper around ICarrierMessagingService to enable the carrier messaging app to implement
     * methods it cares about in the {@link ICarrierMessagingService} interface.
     */
    private class ICarrierMessagingWrapper extends ICarrierMessagingService.Stub {
        @Override
        public void filterSms(MessagePdu pdu, String format, int destPort,
                              int subId, final ICarrierMessagingCallback callback) {
            onReceiveTextSms(pdu, format, destPort, subId,
                new ResultCallback<Integer>() {
                    @Override
                    public void onReceiveResult(Integer options) throws RemoteException {
                        callback.onFilterComplete(options);
                    }
                });
        }

        @Override
        public void sendTextSms(String text, int subId, String destAddress,
                int sendSmsFlag, final ICarrierMessagingCallback callback) {
            onSendTextSms(text, subId, destAddress, sendSmsFlag,
                    new ResultCallback<SendSmsResult>() {
                    @Override
                    public void onReceiveResult(final SendSmsResult result) throws RemoteException {
                        callback.onSendSmsComplete(result.getSendStatus(), result.getMessageRef());
                    }
                });
        }

        @Override
        public void sendDataSms(byte[] data, int subId, String destAddress, int destPort,
                int sendSmsFlag, final ICarrierMessagingCallback callback) {
            onSendDataSms(data, subId, destAddress, destPort, sendSmsFlag,
                    new ResultCallback<SendSmsResult>() {
                    @Override
                    public void onReceiveResult(final SendSmsResult result) throws RemoteException {
                        callback.onSendSmsComplete(result.getSendStatus(), result.getMessageRef());
                    }
                });
        }

        @Override
        public void sendMultipartTextSms(List<String> parts, int subId, String destAddress,
                int sendSmsFlag, final ICarrierMessagingCallback callback) {
            onSendMultipartTextSms(parts, subId, destAddress, sendSmsFlag,
                        new ResultCallback<SendMultipartSmsResult>() {
                                @Override
                                public void onReceiveResult(final SendMultipartSmsResult result)
                                        throws RemoteException {
                                    callback.onSendMultipartSmsComplete(
                                            result.getSendStatus(), result.getMessageRefs());
                                }
                            });
        }

        @Override
        public void sendMms(Uri pduUri, int subId, Uri location,
                final ICarrierMessagingCallback callback) {
            onSendMms(pduUri, subId, location, new ResultCallback<SendMmsResult>() {
                    @Override
                    public void onReceiveResult(final SendMmsResult result) throws RemoteException {
                        callback.onSendMmsComplete(result.getSendStatus(), result.getSendConfPdu());
                    }
                });
        }

        @Override
        public void downloadMms(Uri pduUri, int subId, Uri location,
                final ICarrierMessagingCallback callback) {
            onDownloadMms(pduUri, subId, location, new ResultCallback<Integer>() {
                    @Override
                    public void onReceiveResult(Integer result) throws RemoteException {
                        callback.onDownloadMmsComplete(result);
                    }
                });
        }
    }
}
