958 lines
38 KiB
Java
958 lines
38 KiB
Java
/*
|
||
* 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;
|
||
}
|
||
}
|