/*
 * Copyright (C) 2018 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.telephony.emergency;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.hardware.radio.voice.EmergencyServiceCategory;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.telephony.Rlog;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * A parcelable class that wraps and retrieves the information of number, service category(s) and
 * country code for a specific emergency number.
 */
public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNumber> {

    private static final String LOG_TAG = "EmergencyNumber";

    /**
     * Defining Emergency Service Category as follows:
     *  - General emergency call, all categories;
     *  - Police;
     *  - Ambulance;
     *  - Fire Brigade;
     *  - Marine Guard;
     *  - Mountain Rescue;
     *  - Manually Initiated eCall (MIeC);
     *  - Automatically Initiated eCall (AIeC);
     *
     * Category UNSPECIFIED (General emergency call, all categories) indicates that no specific
     * services are associated with this emergency number; if the emergency number is specified,
     * it has one or more defined emergency service categories.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     *
     * @hide
     */
    @IntDef(flag = true, prefix = { "EMERGENCY_SERVICE_CATEGORY_" }, value = {
            EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
            EMERGENCY_SERVICE_CATEGORY_POLICE,
            EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
            EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
            EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD,
            EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE,
            EMERGENCY_SERVICE_CATEGORY_MIEC,
            EMERGENCY_SERVICE_CATEGORY_AIEC
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface EmergencyServiceCategories {}

    /**
     * Emergency Service Category UNSPECIFIED (General emergency call, all categories) bit-field
     * indicates that no specific services are associated with this emergency number; if the
     * emergency number is specified, it has one or more defined emergency service categories.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED =
            EmergencyServiceCategory.UNSPECIFIED;
    /**
     * Bit-field that indicates Emergency Service Category for Police.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_POLICE = EmergencyServiceCategory.POLICE;
    /**
     * Bit-field that indicates Emergency Service Category for Ambulance.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_AMBULANCE =
            EmergencyServiceCategory.AMBULANCE;
    /**
     * Bit-field that indicates Emergency Service Category for Fire Brigade.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE =
            EmergencyServiceCategory.FIRE_BRIGADE;
    /**
     * Bit-field that indicates Emergency Service Category for Marine Guard.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD =
            EmergencyServiceCategory.MARINE_GUARD;
    /**
     * Bit-field that indicates Emergency Service Category for Mountain Rescue.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE =
            EmergencyServiceCategory.MOUNTAIN_RESCUE;
    /**
     * Bit-field that indicates Emergency Service Category for Manually Initiated eCall (MIeC)
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_MIEC = EmergencyServiceCategory.MIEC;
    /**
     * Bit-field that indicates Emergency Service Category for Automatically Initiated eCall (AIeC)
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_SERVICE_CATEGORY_AIEC = EmergencyServiceCategory.AIEC;

    private static final Set<Integer> EMERGENCY_SERVICE_CATEGORY_SET;
    static {
        EMERGENCY_SERVICE_CATEGORY_SET = new HashSet<Integer>();
        EMERGENCY_SERVICE_CATEGORY_SET.add(EMERGENCY_SERVICE_CATEGORY_POLICE);
        EMERGENCY_SERVICE_CATEGORY_SET.add(EMERGENCY_SERVICE_CATEGORY_AMBULANCE);
        EMERGENCY_SERVICE_CATEGORY_SET.add(EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE);
        EMERGENCY_SERVICE_CATEGORY_SET.add(EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD);
        EMERGENCY_SERVICE_CATEGORY_SET.add(EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE);
        EMERGENCY_SERVICE_CATEGORY_SET.add(EMERGENCY_SERVICE_CATEGORY_MIEC);
        EMERGENCY_SERVICE_CATEGORY_SET.add(EMERGENCY_SERVICE_CATEGORY_AIEC);
    }

    /**
     * The source to tell where the corresponding @1.4::EmergencyNumber comes from.
     *
     * The emergency number has one or more defined emergency number sources.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     *
     * @hide
     */
    @IntDef(flag = true, prefix = { "EMERGENCY_NUMBER_SOURCE_" }, value = {
            EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
            EMERGENCY_NUMBER_SOURCE_SIM,
            EMERGENCY_NUMBER_SOURCE_DATABASE,
            EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG,
            EMERGENCY_NUMBER_SOURCE_DEFAULT
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface EmergencyNumberSources {}

    /**
     * Bit-field which indicates the number is from the network signaling.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING =
            android.hardware.radio.voice.EmergencyNumber.SOURCE_NETWORK_SIGNALING;
    /**
     * Bit-field which indicates the number is from the sim.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_NUMBER_SOURCE_SIM =
            android.hardware.radio.voice.EmergencyNumber.SOURCE_SIM;
    /**
     * Bit-field which indicates the number is from the platform-maintained database.
     */
    public static final int EMERGENCY_NUMBER_SOURCE_DATABASE =  1 << 4;
    /**
     * Bit-field which indicates the number is from test mode.
     *
     * @hide
     */
    @TestApi
    public static final int EMERGENCY_NUMBER_SOURCE_TEST =  1 << 5;
    /** Bit-field which indicates the number is from the modem config. */
    public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG =
            android.hardware.radio.voice.EmergencyNumber.SOURCE_MODEM_CONFIG;
    /**
     * Bit-field which indicates the number is available as default.
     *
     * 112, 911 must always be available; additionally, 000, 08, 110, 999, 118 and 119 must be
     * available when sim is not present.
     *
     * Reference: 3gpp 22.101, Section 10 - Emergency Calls
     */
    public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT =
            android.hardware.radio.voice.EmergencyNumber.SOURCE_DEFAULT;

    private static final Set<Integer> EMERGENCY_NUMBER_SOURCE_SET;
    static {
        EMERGENCY_NUMBER_SOURCE_SET = new HashSet<Integer>();
        EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
        EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_SIM);
        EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_DATABASE);
        EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG);
        EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_DEFAULT);
    }

    /**
     * Indicated the framework does not know whether an emergency call should be placed using
     * emergency or normal call routing. This means the underlying radio or IMS implementation is
     * free to determine for itself how to route the call.
     */
    public static final int EMERGENCY_CALL_ROUTING_UNKNOWN = 0;
    /**
     * Indicates the radio or IMS implementation must handle the call through emergency routing.
     */
    public static final int EMERGENCY_CALL_ROUTING_EMERGENCY = 1;
    /**
     * Indicates the radio or IMS implementation must handle the call through normal call routing.
     */
    public static final int EMERGENCY_CALL_ROUTING_NORMAL = 2;

    /**
     * The routing to tell how to handle the call for the corresponding emergency number.
     *
     * @hide
     */
    @IntDef(flag = false, prefix = { "EMERGENCY_CALL_ROUTING_" }, value = {
            EMERGENCY_CALL_ROUTING_UNKNOWN,
            EMERGENCY_CALL_ROUTING_EMERGENCY,
            EMERGENCY_CALL_ROUTING_NORMAL
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface EmergencyCallRouting {}


    private final String mNumber;
    private final String mCountryIso;
    private final String mMnc;
    private final int mEmergencyServiceCategoryBitmask;
    private final List<String> mEmergencyUrns;
    private final int mEmergencyNumberSourceBitmask;
    private final int mEmergencyCallRouting;
    /**
     * The source of the EmergencyNumber in the order of precedence.
     */
    private static final int[] EMERGENCY_NUMBER_SOURCE_PRECEDENCE;
    static {
        EMERGENCY_NUMBER_SOURCE_PRECEDENCE = new int[4];
        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[0] = EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING;
        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[1] = EMERGENCY_NUMBER_SOURCE_SIM;
        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[2] = EMERGENCY_NUMBER_SOURCE_DATABASE;
        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[3] = EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG;
    }

    /** @hide */
    public EmergencyNumber(@NonNull String number, @NonNull String countryIso, @NonNull String mnc,
                           @EmergencyServiceCategories int emergencyServiceCategories,
                           @NonNull List<String> emergencyUrns,
                           @EmergencyNumberSources int emergencyNumberSources,
                           @EmergencyCallRouting int emergencyCallRouting) {
        this.mNumber = number;
        this.mCountryIso = countryIso;
        this.mMnc = mnc;
        this.mEmergencyServiceCategoryBitmask = emergencyServiceCategories;
        this.mEmergencyUrns = emergencyUrns;
        this.mEmergencyNumberSourceBitmask = emergencyNumberSources;
        this.mEmergencyCallRouting = emergencyCallRouting;
    }

    /** @hide */
    public EmergencyNumber(Parcel source) {
        mNumber = source.readString();
        mCountryIso = source.readString();
        mMnc = source.readString();
        mEmergencyServiceCategoryBitmask = source.readInt();
        mEmergencyUrns = source.createStringArrayList();
        mEmergencyNumberSourceBitmask = source.readInt();
        mEmergencyCallRouting = source.readInt();
    }

    @Override
    /** @hide */
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mNumber);
        dest.writeString(mCountryIso);
        dest.writeString(mMnc);
        dest.writeInt(mEmergencyServiceCategoryBitmask);
        dest.writeStringList(mEmergencyUrns);
        dest.writeInt(mEmergencyNumberSourceBitmask);
        dest.writeInt(mEmergencyCallRouting);
    }

    public static final @NonNull Creator<EmergencyNumber> CREATOR =
            new Creator<EmergencyNumber>() {
                @Override
                public EmergencyNumber createFromParcel(Parcel in) {
                    return new EmergencyNumber(in);
                }

                @Override
                public EmergencyNumber[] newArray(int size) {
                    return new EmergencyNumber[size];
                }
            };

    /**
     * Get the dialing number of the emergency number.
     *
     * The character in the number string is only the dial pad
     * character('0'-'9', '*', '+', or '#'). For example: 911.
     *
     * If the number starts with carrier prefix, the carrier prefix is configured in
     * {@link CarrierConfigManager#KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY}.
     *
     * @return the dialing number.
     */
    public @NonNull String getNumber() {
        return mNumber;
    }

    /**
     * Get the country code string (lowercase character) in ISO 3166 format of the emergency number.
     *
     * @return the country code string (lowercase character) in ISO 3166 format.
     */
    public @NonNull String getCountryIso() {
        return mCountryIso;
    }

    /**
     * Get the Mobile Network Code of the emergency number.
     *
     * @return the Mobile Network Code of the emergency number.
     */
    public @NonNull String getMnc() {
        return mMnc;
    }

    /**
     * Returns the bitmask of emergency service categories of the emergency number.
     *
     * @return bitmask of the emergency service categories
     *
     * @hide
     */
    public @EmergencyServiceCategories int getEmergencyServiceCategoryBitmask() {
        return mEmergencyServiceCategoryBitmask;
    }

    /**
     * Returns the bitmask of emergency service categories of the emergency number for
     * internal dialing.
     *
     * @return bitmask of the emergency service categories
     *
     * @hide
     */
    public @EmergencyServiceCategories int getEmergencyServiceCategoryBitmaskInternalDial() {
        if (mEmergencyNumberSourceBitmask == EMERGENCY_NUMBER_SOURCE_DATABASE) {
            return EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
        }
        return mEmergencyServiceCategoryBitmask;
    }

    /**
     * Returns the emergency service categories of the emergency number.
     *
     * Note: if the emergency number is in {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}, only
     * {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED} is returned and it means the number is in
     * all categories.
     *
     * @return a list of the emergency service categories
     */
    public @NonNull List<Integer> getEmergencyServiceCategories() {
        List<Integer> categories = new ArrayList<>();
        if (serviceUnspecified()) {
            categories.add(EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED);
            return categories;
        }
        for (Integer category : EMERGENCY_SERVICE_CATEGORY_SET) {
            if (isInEmergencyServiceCategories(category)) {
                categories.add(category);
            }
        }
        return categories;
    }

    /**
     * Returns the list of emergency Uniform Resources Names (URN) of the emergency number.
     *
     * For example, {@code urn:service:sos} is the generic URN for contacting emergency services
     * of all type.
     *
     * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
     *            RFC 5031
     *
     * @return list of emergency Uniform Resources Names (URN) or an empty list if the emergency
     *         number does not have a specified emergency Uniform Resource Name.
     */
    public @NonNull List<String> getEmergencyUrns() {
        return Collections.unmodifiableList(mEmergencyUrns);
    }

    /**
     * Checks if the emergency service category is unspecified for the emergency number
     * {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}.
     *
     * @return {@code true} if the emergency service category is unspecified for the emergency
     * number {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}; {@code false} otherwise.
     */
    private boolean serviceUnspecified() {
        return mEmergencyServiceCategoryBitmask == EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
    }

    /**
     * Checks if the emergency number is in the supplied emergency service category(s).
     *
     * @param categories - the supplied emergency service categories
     *
     * @return {@code true} if the emergency number is in the specified emergency service
     * category(s) or if its emergency service category is
     * {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}; {@code false} otherwise.
     */
    public boolean isInEmergencyServiceCategories(@EmergencyServiceCategories int categories) {
        if (categories == EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED) {
            return serviceUnspecified();
        }
        if (serviceUnspecified()) {
            return true;
        }
        return (mEmergencyServiceCategoryBitmask & categories) == categories;
    }

    /**
     * Returns the bitmask of the sources of the emergency number.
     *
     * @return bitmask of the emergency number sources
     *
     * @hide
     */
    public @EmergencyNumberSources int getEmergencyNumberSourceBitmask() {
        return mEmergencyNumberSourceBitmask;
    }

    /**
     * Returns a list of sources of the emergency number.
     *
     * @return a list of emergency number sources
     */
    public @NonNull List<Integer> getEmergencyNumberSources() {
        List<Integer> sources = new ArrayList<>();
        for (Integer source : EMERGENCY_NUMBER_SOURCE_SET) {
            if ((mEmergencyNumberSourceBitmask & source) == source) {
                sources.add(source);
            }
        }
        return sources;
    }

    /**
     * Checks if the emergency number is from the specified emergency number source(s).
     *
     * @return {@code true} if the emergency number is from the specified emergency number
     * source(s); {@code false} otherwise.
     *
     * @param sources - the supplied emergency number sources
     */
    public boolean isFromSources(@EmergencyNumberSources int sources) {
        return (mEmergencyNumberSourceBitmask & sources) == sources;
    }

    /**
     * Returns the emergency call routing information.
     *
     * <p>Some regions require some emergency numbers which are not routed using typical emergency
     * call processing, but are instead placed as regular phone calls. The emergency call routing
     * field provides information about how an emergency call will be routed when it is placed.
     *
     * @return the emergency call routing requirement
     */
    public @EmergencyCallRouting int getEmergencyCallRouting() {
        return mEmergencyCallRouting;
    }

    @Override
    /** @hide */
    public int describeContents() {
        return 0;
    }

    @Override
    public String toString() {
        return String.format("[EmergencyNumber: %s, countryIso=%s, mnc=%s, src=%s, routing=%s, "
                        + "categories=%s, urns=%s]",
                mNumber,
                mCountryIso,
                mMnc,
                sourceBitmaskToString(mEmergencyNumberSourceBitmask),
                routingToString(mEmergencyCallRouting),
                categoriesToString(mEmergencyServiceCategoryBitmask),
                (mEmergencyUrns == null ? "" :
                        mEmergencyUrns.stream().collect(Collectors.joining(","))));
    }

    /**
     * @param categories emergency service category bitmask
     * @return loggable string describing the category bitmask
     */
    private String categoriesToString(@EmergencyServiceCategories int categories) {
        StringBuilder sb = new StringBuilder();
        if ((categories & EMERGENCY_SERVICE_CATEGORY_AIEC) == EMERGENCY_SERVICE_CATEGORY_AIEC) {
            sb.append("auto ");
        }
        if ((categories & EMERGENCY_SERVICE_CATEGORY_AMBULANCE)
                == EMERGENCY_SERVICE_CATEGORY_AMBULANCE) {
            sb.append("ambulance ");
        }
        if ((categories & EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE)
                == EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE) {
            sb.append("fire ");
        }
        if ((categories & EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD)
                == EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD) {
            sb.append("marine ");
        }
        if ((categories & EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE)
                == EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE) {
            sb.append("mountain ");
        }
        if ((categories & EMERGENCY_SERVICE_CATEGORY_POLICE) == EMERGENCY_SERVICE_CATEGORY_POLICE) {
            sb.append("police ");
        }
        if ((categories & EMERGENCY_SERVICE_CATEGORY_MIEC) == EMERGENCY_SERVICE_CATEGORY_MIEC) {
            sb.append("manual ");
        }
        return sb.toString();
    }

    /**
     * @param routing emergency call routing type
     * @return loggable string describing the routing type.
     */
    private String routingToString(@EmergencyCallRouting int routing) {
        return switch(routing) {
            case EMERGENCY_CALL_ROUTING_EMERGENCY -> "emergency";
            case EMERGENCY_CALL_ROUTING_NORMAL -> "normal";
            case EMERGENCY_CALL_ROUTING_UNKNOWN -> "unknown";
            default -> "🤷";
        };
    }

    /**
     * Builds a string describing the sources for an emergency number.
     * @param sourceBitmask the source bitmask
     * @return loggable string describing the sources.
     */
    private String sourceBitmaskToString(@EmergencyNumberSources int sourceBitmask) {
        StringBuilder sb = new StringBuilder();
        if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING)
                == EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING) {
            sb.append("net ");
        }
        if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_SIM) == EMERGENCY_NUMBER_SOURCE_SIM) {
            sb.append("sim ");
        }
        if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_DATABASE)
                == EMERGENCY_NUMBER_SOURCE_DATABASE) {
            sb.append("db ");
        }
        if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG)
                == EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG) {
            sb.append("mdm ");
        }
        if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_DEFAULT) == EMERGENCY_NUMBER_SOURCE_DEFAULT) {
            sb.append("def ");
        }
        if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_TEST) == EMERGENCY_NUMBER_SOURCE_TEST) {
            sb.append("tst ");
        }
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (!EmergencyNumber.class.isInstance(o)) {
            return false;
        }
        EmergencyNumber other = (EmergencyNumber) o;
        return mNumber.equals(other.mNumber)
                && mCountryIso.equals(other.mCountryIso)
                && mMnc.equals(other.mMnc)
                && mEmergencyServiceCategoryBitmask == other.mEmergencyServiceCategoryBitmask
                && mEmergencyUrns.equals(other.mEmergencyUrns)
                && mEmergencyNumberSourceBitmask == other.mEmergencyNumberSourceBitmask
                && mEmergencyCallRouting == other.mEmergencyCallRouting;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mNumber, mCountryIso, mMnc, mEmergencyServiceCategoryBitmask,
                mEmergencyUrns, mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
    }

    /**
     * Calculate the score for display priority.
     *
     * A higher display priority score means the emergency number has a higher display priority.
     * The score is higher if the source is defined for a higher display priority.
     *
     * The priority of sources are defined as follows:
     *     EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING >
     *     EMERGENCY_NUMBER_SOURCE_SIM >
     *     EMERGENCY_NUMBER_SOURCE_DATABASE >
     *     EMERGENCY_NUMBER_SOURCE_DEFAULT >
     *     EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG
     *
     */
    private int getDisplayPriorityScore() {
        int score = 0;
        if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING)) {
            score += 1 << 4;
        }
        if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_SIM)) {
            score += 1 << 3;
        }
        if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
            score += 1 << 2;
        }
        if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_DEFAULT)) {
            score += 1 << 1;
        }
        if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG)) {
            score += 1 << 0;
        }
        return score;
    }

    /**
     * Compare the display priority for this emergency number and the supplied emergency number.
     *
     * @param emergencyNumber the supplied emergency number
     * @return a negative value if the supplied emergency number has a lower display priority;
     *         a positive value if the supplied emergency number has a higher display priority;
     *         0 if both have equal display priority.
     */
    @Override
    public int compareTo(@NonNull EmergencyNumber emergencyNumber) {
        if (this.getDisplayPriorityScore()
                > emergencyNumber.getDisplayPriorityScore()) {
            return -1;
        } else if (this.getDisplayPriorityScore()
                < emergencyNumber.getDisplayPriorityScore()) {
            return 1;
        } else if (this.getNumber().compareTo(emergencyNumber.getNumber()) != 0) {
            return this.getNumber().compareTo(emergencyNumber.getNumber());
        } else if (this.getCountryIso().compareTo(emergencyNumber.getCountryIso()) != 0) {
            return this.getCountryIso().compareTo(emergencyNumber.getCountryIso());
        } else if (this.getMnc().compareTo(emergencyNumber.getMnc()) != 0) {
            return this.getMnc().compareTo(emergencyNumber.getMnc());
        } else if (this.getEmergencyServiceCategoryBitmask()
                != emergencyNumber.getEmergencyServiceCategoryBitmask()) {
            return this.getEmergencyServiceCategoryBitmask()
                    > emergencyNumber.getEmergencyServiceCategoryBitmask() ? -1 : 1;
        } else if (this.getEmergencyUrns().toString().compareTo(
                emergencyNumber.getEmergencyUrns().toString()) != 0) {
            return this.getEmergencyUrns().toString().compareTo(
                    emergencyNumber.getEmergencyUrns().toString());
        } else if (this.getEmergencyCallRouting()
                != emergencyNumber.getEmergencyCallRouting()) {
            return this.getEmergencyCallRouting()
                    > emergencyNumber.getEmergencyCallRouting() ? -1 : 1;
        } else {
            return 0;
        }
    }

    /**
     * In-place merge same emergency numbers in the emergency number list.
     *
     * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’, 'mnc' and
     * 'categories' fields. Multiple Emergency Number Sources should be merged into one bitfield
     * for the same EmergencyNumber.
     *
     * @param emergencyNumberList the emergency number list to process
     *
     * @hide
     */
    public static void mergeSameNumbersInEmergencyNumberList(
            List<EmergencyNumber> emergencyNumberList) {
        mergeSameNumbersInEmergencyNumberList(emergencyNumberList, false);
    }

    /**
     * In-place merge same emergency numbers in the emergency number list.
     *
     * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’ and 'mnc' fields.
     * If mergeServiceCategoriesAndUrns is true ignore comparing of 'urns' and
     * 'categories' fields and determine these fields from most precedent number. Else compare
     * to get unique combination of EmergencyNumber.
     * Multiple Emergency Number Sources should be merged into one bitfield for the
     * same EmergencyNumber.
     *
     * @param emergencyNumberList the emergency number list to process
     * @param mergeServiceCategoriesAndUrns {@code true} determine service category and urns
     * from most precedent number. {@code false} compare those fields for determing duplicate.
     *
     * @hide
     */
    public static void mergeSameNumbersInEmergencyNumberList(
            @NonNull List<EmergencyNumber> emergencyNumberList,
            boolean mergeServiceCategoriesAndUrns) {
        if (emergencyNumberList == null) {
            return;
        }

        Set<Integer> duplicatedEmergencyNumberPosition = new HashSet<>();
        for (int i = 0; i < emergencyNumberList.size(); i++) {
            for (int j = 0; j < i; j++) {
                if (areSameEmergencyNumbers(emergencyNumberList.get(i),
                        emergencyNumberList.get(j), mergeServiceCategoriesAndUrns)) {
                    Rlog.e(LOG_TAG, "Found unexpected duplicate numbers "
                            + emergencyNumberList.get(i)
                            + " vs " + emergencyNumberList.get(j));
                    // Set the merged emergency number in the current position
                    emergencyNumberList.set(i,
                            mergeSameEmergencyNumbers(emergencyNumberList.get(i),
                            emergencyNumberList.get(j), mergeServiceCategoriesAndUrns));
                    // Mark the emergency number has been merged
                    duplicatedEmergencyNumberPosition.add(j);
                }
            }
        }

        // Remove the marked emergency number in the original list
        for (int i = emergencyNumberList.size() - 1; i >= 0; i--) {
            if (duplicatedEmergencyNumberPosition.contains(i)) {
                emergencyNumberList.remove(i);
            }
        }
        Collections.sort(emergencyNumberList);
    }

    /**
     * Check if two emergency numbers are the same.
     *
     * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’, 'mnc' fields.
     * If mergeServiceCategoriesAndUrns is true ignore comparing of 'urns' and
     * 'categories' fields and determine these fields from most precedent number. Else compare
     * to get unique combination of EmergencyNumber.
     * Multiple Emergency Number Sources should be
     * merged into one bitfield for the same EmergencyNumber.
     *
     * @param first first EmergencyNumber to compare
     * @param second second EmergencyNumber to compare
     * @param ignoreServiceCategoryAndUrns {@code true} Ignore comparing of service category
     * and Urns so that they can be determined from most precedent number. {@code false} compare
     * those fields for determing duplicate.
     * @return true if they are the same EmergencyNumbers; false otherwise.
     *
     * @hide
     */
    public static boolean areSameEmergencyNumbers(@NonNull EmergencyNumber first,
            @NonNull EmergencyNumber second, boolean ignoreServiceCategoryAndUrns) {
        if (!first.getNumber().equals(second.getNumber())) {
            return false;
        }
        if (!first.getCountryIso().equals(second.getCountryIso())) {
            return false;
        }
        if (!first.getMnc().equals(second.getMnc())) {
            return false;
        }
        if (!ignoreServiceCategoryAndUrns) {
            if (first.getEmergencyServiceCategoryBitmask()
                    != second.getEmergencyServiceCategoryBitmask()) {
                return false;
            }
            if (!first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
                return false;
            }
        }
        // Never merge two numbers if one of them is from test mode but the other one is not;
        // This supports to remove a number from the test mode.
        if (first.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST)
                ^ second.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST)) {
            return false;
        }
        return true;
    }

    /**
     * Get a merged EmergencyNumber from two same emergency numbers. Two emergency numbers are
     * the same if {@link #areSameEmergencyNumbers} returns {@code true}.
     *
     * @param first first EmergencyNumber to compare
     * @param second second EmergencyNumber to compare
     * @return a merged EmergencyNumber or null if they are not the same EmergencyNumber
     *
     * @hide
     */
    public static EmergencyNumber mergeSameEmergencyNumbers(@NonNull EmergencyNumber first,
                                                            @NonNull EmergencyNumber second) {
        if (areSameEmergencyNumbers(first, second, false)) {
            int routing = first.getEmergencyCallRouting();

            if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
                routing = second.getEmergencyCallRouting();
            }

            return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
                    first.getEmergencyServiceCategoryBitmask(),
                    first.getEmergencyUrns(),
                    first.getEmergencyNumberSourceBitmask()
                            | second.getEmergencyNumberSourceBitmask(),
                    routing);
        }
        return null;
    }

    /**
     * Get merged EmergencyUrns list from two same emergency numbers.
     * By giving priority to the urns from first number.
     *
     * @param firstEmergencyUrns first number's Urns
     * @param secondEmergencyUrns second number's Urns
     * @return a merged Urns
     *
     * @hide
     */
    private static List<String> mergeEmergencyUrns(@NonNull List<String> firstEmergencyUrns,
            @NonNull List<String> secondEmergencyUrns) {
        List<String> mergedUrns = new ArrayList<String>();
        mergedUrns.addAll(firstEmergencyUrns);
        for (String urn : secondEmergencyUrns) {
            if (!firstEmergencyUrns.contains(urn)) {
                mergedUrns.add(urn);
            }
        }
        return mergedUrns;
    }

    /**
     * Get the highest precedence source of the given Emergency number. Then get service catergory
     * and urns list fill in the respective map with key as source.
     *
     * @param num EmergencyNumber to get the source, service category & urns
     * @param serviceCategoryArray Array to store the category of the given EmergencyNumber
     * with key as highest precedence source
     * @param urnsArray Array to store the list of Urns of the given EmergencyNumber
     * with key as highest precedence source
     *
     * @hide
     */
    private static void fillServiceCategoryAndUrns(@NonNull EmergencyNumber num,
            @NonNull SparseIntArray serviceCategoryArray,
            @NonNull SparseArray<List<String>> urnsArray) {
        int numberSrc = num.getEmergencyNumberSourceBitmask();
        for (Integer source : EMERGENCY_NUMBER_SOURCE_PRECEDENCE) {
            if ((numberSrc & source) == source) {
                if (!num.isInEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED)) {
                    serviceCategoryArray.put(source, num.getEmergencyServiceCategoryBitmask());
                }
                urnsArray.put(source, num.getEmergencyUrns());
                break;
            }
        }
    }

    /**
     * Get a merged EmergencyNumber from two same emergency numbers from
     * Emergency number list. Two emergency numbers are the same if
     * {@link #areSameEmergencyNumbers} returns {@code true}.
     *
     * @param first first EmergencyNumber to compare
     * @param second second EmergencyNumber to compare
     * @param mergeServiceCategoriesAndUrns {@code true} then determine service category and urns
     * Service catetory : set from most precedence source number(N/W, SIM, DB, modem_cfg)
     * Urns : merge from both with first priority from most precedence source number
     * {@code false} then call {@link #mergeSameEmergencyNumbers} to merge.
     * @return a merged EmergencyNumber or null if they are not the same EmergencyNumber
     *
     * @hide
     */
    public static @NonNull EmergencyNumber mergeSameEmergencyNumbers(
            @NonNull EmergencyNumber first, @NonNull EmergencyNumber second,
            boolean mergeServiceCategoriesAndUrns) {
        if (!mergeServiceCategoriesAndUrns) {
            return mergeSameEmergencyNumbers(first, second);
        }

        int routing = first.getEmergencyCallRouting();
        int serviceCategory = first.getEmergencyServiceCategoryBitmask();
        List<String> mergedEmergencyUrns = new ArrayList<String>();
        //Maps to store the service category and urns of both the first and second emergency number
        // with key as most precedent source
        SparseIntArray serviceCategoryArray = new SparseIntArray(2);
        SparseArray<List<String>> urnsArray = new SparseArray(2);

        fillServiceCategoryAndUrns(first, serviceCategoryArray, urnsArray);
        fillServiceCategoryAndUrns(second, serviceCategoryArray, urnsArray);

        if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
            routing = second.getEmergencyCallRouting();
        }

        // Determine serviceCategory of most precedence number
        for (int sourceOfCategory : EMERGENCY_NUMBER_SOURCE_PRECEDENCE) {
            if (serviceCategoryArray.indexOfKey(sourceOfCategory) >= 0) {
                serviceCategory = serviceCategoryArray.get(sourceOfCategory);
                break;
            }
        }

        // Merge Urns in precedence number
        for (int sourceOfUrn : EMERGENCY_NUMBER_SOURCE_PRECEDENCE) {
            if (urnsArray.contains(sourceOfUrn)) {
                mergedEmergencyUrns = mergeEmergencyUrns(mergedEmergencyUrns,
                        urnsArray.get(sourceOfUrn));
            }
        }

        return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
                serviceCategory, mergedEmergencyUrns,
                first.getEmergencyNumberSourceBitmask()
                        | second.getEmergencyNumberSourceBitmask(),
                routing);
    }

    /**
     * Validate Emergency Number address that only contains the dialable character
     * {@link PhoneNumberUtils#isDialable(char)}
     *
     * @hide
     */
    public static boolean validateEmergencyNumberAddress(String address) {
        if (address == null) {
            return false;
        }
        for (char c : address.toCharArray()) {
            if (!PhoneNumberUtils.isDialable(c)) {
                return false;
            }
        }
        return true;
    }
}
