Merge "Added a broadcast intent for clients to listen for country changes"
diff --git a/ecc/README.md b/ecc/README.md
index b5dc538..304cdfb 100644
--- a/ecc/README.md
+++ b/ecc/README.md
@@ -30,4 +30,4 @@
4. Make TeleService
5. Push TeleService.apk to system/priv-app/TeleService
6. Reboot device
-7. run 'atest TeleServiceTests:IsoToEccProtobufRepositoryTest#testEccDataContent'
+7. run 'atest TeleServiceTests:EccDataTest#testEccDataContent'
diff --git a/ecc/gen_eccdata.sh b/ecc/gen_eccdata.sh
index 4f3a097..e4dd745 100755
--- a/ecc/gen_eccdata.sh
+++ b/ecc/gen_eccdata.sh
@@ -57,4 +57,4 @@
echo " 1. make TeleService"
echo " 2. push TeleService.apk to system/priv-app/TeleService"
echo " 3. reboot device"
-echo " 4. run 'atest TeleServiceTests:IsoToEccProtobufRepositoryTest#testEccDataContent'"
+echo " 4. run 'atest TeleServiceTests:EccDataTest#testEccDataContent'"
diff --git a/src/com/android/phone/EccShortcutAdapter.java b/src/com/android/phone/EccShortcutAdapter.java
index e14b90a..19b1fec 100644
--- a/src/com/android/phone/EccShortcutAdapter.java
+++ b/src/com/android/phone/EccShortcutAdapter.java
@@ -17,6 +17,7 @@
package com.android.phone;
import android.content.Context;
+import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -25,9 +26,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.phone.ecc.CountryEccInfo;
-import com.android.phone.ecc.EccInfo;
-
import com.google.common.collect.LinkedListMultimap;
import java.util.ArrayList;
@@ -35,15 +33,11 @@
/**
* An abstract adapter between ECC data and the view contains ECC shortcuts.
- * This adapter will convert given {@link CountryEccInfo} to number string, description string and
- * icon resource id for each {@link EccInfo}.
+ * This adapter prepares description and icon for every promoted emergency number.
* The subclass should implements {@link #inflateView} to provide the view for an ECC data, when the
* view container calls {@link #getView}.
*/
public abstract class EccShortcutAdapter extends BaseAdapter {
- // GSM default emergency number, used when country's fallback ECC(112 or 911) not available.
- private static final String FALLBACK_EMERGENCY_NUMBER = "112";
-
private List<EccDisplayMaterial> mEccDisplayMaterialList;
private CharSequence mPoliceDescription;
@@ -90,16 +84,16 @@
* Get a View that display the given ECC data: number, description and iconRes.
*
* @param convertView The old view to reuse, if possible. Note: You should check that this view
- * is non-null and of an appropriate type before using. If it is not possible
- * to convert this view to display the correct data, this method can create a
- * new view. Heterogeneous lists can specify their number of view types, so
- * that this View is always of the right type (see {@link
- * BaseAdapter#getViewTypeCount()} and {@link
- * BaseAdapter#getItemViewType(int)}).
- * @param parent The parent that this view will eventually be attached to.
- * @param number The number of the ECC shortcut to display in the view.
+ * is non-null and of an appropriate type before using. If it is not possible
+ * to convert this view to display the correct data, this method can create a
+ * new view. Heterogeneous lists can specify their number of view types, so
+ * that this View is always of the right type (see {@link
+ * BaseAdapter#getViewTypeCount()} and {@link
+ * BaseAdapter#getItemViewType(int)}).
+ * @param parent The parent that this view will eventually be attached to.
+ * @param number The number of the ECC shortcut to display in the view.
* @param description The description of the ECC shortcut to display in the view.
- * @param iconRes The icon resource ID represent for the ECC shortcut.
+ * @param iconRes The icon resource ID represent for the ECC shortcut.
* @return A View corresponding to the data at the specified position.
*/
public abstract View inflateView(View convertView, ViewGroup parent, CharSequence number,
@@ -110,89 +104,68 @@
* be display by the short container View.
*
* @param context The context used to access resources.
- * @param countryEccInfo Updated country ECC info.
+ * @param phoneInfo Information of the phone to make an emergency call.
*/
- public void updateCountryEccInfo(@NonNull Context context, CountryEccInfo countryEccInfo) {
+ public void updateCountryEccInfo(@NonNull Context context,
+ @Nullable ShortcutViewUtils.PhoneInfo phoneInfo) {
List<EccDisplayMaterial> displayMaterials = new ArrayList<>();
- final EccInfo.Type[] orderedMustHaveTypes =
- { EccInfo.Type.POLICE, EccInfo.Type.AMBULANCE, EccInfo.Type.FIRE };
-
- String fallback = null;
- EccInfo[] eccInfoList = null;
- if (countryEccInfo != null) {
- fallback = countryEccInfo.getFallbackEcc();
- eccInfoList = countryEccInfo.getEccInfoList();
- }
- if (TextUtils.isEmpty(fallback)) {
- fallback = FALLBACK_EMERGENCY_NUMBER;
- }
-
- // Finding matched ECC for each must have types.
- // Using LinkedListMultimap to prevent duplicated keys.
- // LinkedListMultimap also preserve the insertion order of keys (ECC number) and values
- // (matched types of the ECC number), which follows the order in orderedMustHaveTypes.
- LinkedListMultimap<String, EccInfo.Type> eccList = LinkedListMultimap.create();
- for (EccInfo.Type type : orderedMustHaveTypes) {
- String number = null;
- if (eccInfoList != null) {
- number = pickEccNumberForType(type, eccInfoList);
+ try {
+ if (phoneInfo == null) {
+ return;
}
- if (number == null) {
- number = fallback;
- }
- // append type for exist number, otherwise insert a new entry.
- eccList.put(number, type);
- }
- // prepare display material for picked ECC
- for (String number : eccList.keySet()) {
- EccDisplayMaterial material = prepareDisplayMaterialForEccInfo(context,
- new EccInfo(number, eccList.asMap().get(number)));
- if (material != null) {
- displayMaterials.add(material);
- }
- }
-
- mEccDisplayMaterialList = displayMaterials;
- notifyDataSetChanged();
- }
-
- private @Nullable String pickEccNumberForType(@NonNull EccInfo.Type targetType,
- @NonNull EccInfo[] eccInfoList) {
- EccInfo pickedEccInfo = null;
- for (EccInfo eccInfo : eccInfoList) {
- if (eccInfo.containsType(targetType)) {
- // An ECC is more suitable for a type if the ECC has fewer other types.
- if (pickedEccInfo == null
- || eccInfo.getTypesCount() < pickedEccInfo.getTypesCount()) {
- pickedEccInfo = eccInfo;
+ LinkedListMultimap<String, Integer> emergencyNumbers = LinkedListMultimap.create();
+ for (int category : ShortcutViewUtils.PROMOTED_CATEGORIES) {
+ String number = pickEmergencyNumberForCategory(category,
+ phoneInfo.getPromotedEmergencyNumbers());
+ if (number != null) {
+ emergencyNumbers.put(number, category);
}
}
+
+ // prepare display material for picked ECC
+ for (String number : emergencyNumbers.keySet()) {
+ EccDisplayMaterial material = prepareEccMaterial(context, number,
+ emergencyNumbers.get(number));
+ if (material != null) {
+ displayMaterials.add(material);
+ }
+ }
+ } finally {
+ mEccDisplayMaterialList = displayMaterials;
+ notifyDataSetChanged();
}
- if (pickedEccInfo != null) {
- return pickedEccInfo.getNumber();
+ }
+
+ @Nullable
+ private String pickEmergencyNumberForCategory(int category,
+ @NonNull List<EmergencyNumber> emergencyNumbers) {
+ for (EmergencyNumber number : emergencyNumbers) {
+ if ((number.getEmergencyServiceCategoryBitmask() & category) != 0) {
+ return number.getNumber();
+ }
}
return null;
}
- private @Nullable EccDisplayMaterial prepareDisplayMaterialForEccInfo(@NonNull Context context,
- @NonNull EccInfo eccInfo) {
+ @Nullable
+ private EccDisplayMaterial prepareEccMaterial(@NonNull Context context, @NonNull String number,
+ @NonNull List<Integer> categories) {
EccDisplayMaterial material = new EccDisplayMaterial();
- material.number = eccInfo.getNumber();
- EccInfo.Type[] types = eccInfo.getTypes();
- for (EccInfo.Type type : types) {
+ material.number = number;
+ for (int category : categories) {
CharSequence description;
- switch (type) {
- case POLICE:
+ switch (category) {
+ case EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE:
description = mPoliceDescription;
material.iconRes = R.drawable.ic_local_police_gm2_24px;
break;
- case AMBULANCE:
+ case EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE:
description = mAmbulanceDescription;
material.iconRes = R.drawable.ic_local_hospital_gm2_24px;
break;
- case FIRE:
+ case EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE:
description = mFireDescription;
material.iconRes = R.drawable.ic_local_fire_department_gm2_24px;
break;
@@ -200,6 +173,7 @@
// ignore unknown types
continue;
}
+
if (TextUtils.isEmpty(material.description)) {
material.description = description;
} else {
@@ -209,10 +183,10 @@
material.description, description);
}
}
+
if (TextUtils.isEmpty(material.description) || material.iconRes == 0) {
return null;
}
return material;
}
-
}
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index c89ddc6..80b4632 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -83,9 +83,6 @@
import com.android.phone.common.dialpad.DialpadKeyButton;
import com.android.phone.common.util.ViewUtil;
import com.android.phone.common.widget.ResizingTextEditText;
-import com.android.phone.ecc.CountryEccInfo;
-import com.android.phone.ecc.EccInfoHelper;
-import com.android.phone.ecc.IsoToEccProtobufRepository;
import java.util.ArrayList;
import java.util.List;
@@ -235,7 +232,7 @@
private View mEmergencyShortcutView;
private View mDialpadView;
- private EccInfoHelper mEccInfoHelper;
+ private ShortcutViewUtils.PhoneInfo mPhoneInfo;
private List<EmergencyShortcutButton> mEmergencyShortcutButtonList;
private EccShortcutAdapter mShortcutAdapter;
@@ -363,14 +360,10 @@
configMgr.getConfigForSubId(SubscriptionManager.getDefaultVoiceSubscriptionId());
mIsShortcutViewEnabled = false;
+ mPhoneInfo = null;
if (canEnableShortcutView(carrierConfig)) {
- TelephonyManager tm = getSystemService(TelephonyManager.class);
- String countryIso = tm.getNetworkCountryIso();
- if (TextUtils.isEmpty(countryIso)) {
- Log.d(LOG_TAG, "Unable to determine the country of current network.");
- } else if (!EccInfoHelper.isCountryEccInfoAvailable(this, countryIso)) {
- Log.d(LOG_TAG, "ECC info is unavailable.");
- } else {
+ mPhoneInfo = ShortcutViewUtils.pickPreferredPhone(this);
+ if (mPhoneInfo != null) {
mIsShortcutViewEnabled = true;
}
}
@@ -469,7 +462,6 @@
mEmergencyInfoGroup = (EmergencyInfoGroup) findViewById(R.id.emergency_info_button);
if (mIsShortcutViewEnabled) {
- mEccInfoHelper = new EccInfoHelper(IsoToEccProtobufRepository.getInstance());
setupEmergencyShortcutsView();
}
}
@@ -611,11 +603,8 @@
if (!TextUtils.isEmpty(phoneNumber)) {
if (DBG) Log.d(LOG_TAG, "dial emergency number: " + Rlog.pii(LOG_TAG, phoneNumber));
- Bundle extras = new Bundle();
- extras.putInt(TelecomManager.EXTRA_CALL_SOURCE,
- ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT);
- TelecomManager tm = (TelecomManager) getSystemService(TELECOM_SERVICE);
- tm.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null), extras);
+ placeCall(phoneNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT,
+ mPhoneInfo);
} else {
Log.d(LOG_TAG, "emergency number is empty");
}
@@ -769,30 +758,9 @@
updateTheme(lockScreenColors.supportsDarkText());
}
- if (mIsShortcutViewEnabled && mEccInfoHelper != null) {
- final Context context = this;
- mEccInfoHelper.getCountryEccInfoAsync(context,
- new EccInfoHelper.CountryEccInfoResultCallback() {
- @Override
- public void onSuccess(String iso, CountryEccInfo countryEccInfo) {
- Log.d(LOG_TAG, "Retrieve ECC info success, country ISO: "
- + Rlog.pii(LOG_TAG, iso));
- updateLocationAndEccInfo(iso, countryEccInfo);
- }
-
- @Override
- public void onDetectCountryFailed() {
- Log.w(LOG_TAG, "Cannot detect current country.");
- updateLocationAndEccInfo(null, null);
- }
-
- @Override
- public void onRetrieveCountryEccInfoFailed(String iso) {
- Log.w(LOG_TAG, "Retrieve ECC info failed, country ISO: "
- + Rlog.pii(LOG_TAG, iso));
- updateLocationAndEccInfo(iso, null);
- }
- });
+ if (mIsShortcutViewEnabled) {
+ mPhoneInfo = ShortcutViewUtils.pickPreferredPhone(this);
+ updateLocationAndEccInfo();
}
}
@@ -897,7 +865,20 @@
// nothing and just returns input number.
mLastNumber = PhoneNumberUtils.convertToEmergencyNumber(this, mLastNumber);
- if (PhoneNumberUtils.isLocalEmergencyNumber(this, mLastNumber)) {
+ boolean isEmergencyNumber = false;
+ ShortcutViewUtils.PhoneInfo phoneToMakeCall = null;
+ if (mPhoneInfo != null) {
+ isEmergencyNumber = mPhoneInfo.hasPromotedEmergencyNumber(mLastNumber);
+ if (isEmergencyNumber) {
+ phoneToMakeCall = mPhoneInfo;
+ }
+ }
+ if (!isEmergencyNumber) {
+ TelephonyManager tm = getSystemService(TelephonyManager.class);
+ isEmergencyNumber = tm.isCurrentEmergencyNumber(mLastNumber);
+ }
+
+ if (isEmergencyNumber) {
if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber);
// place the call if it is a valid number
@@ -910,11 +891,8 @@
mMetricsWriter.writeMetricsForMakingCall(MetricsWriter.CALL_SOURCE_DIALPAD,
MetricsWriter.PHONE_NUMBER_TYPE_EMERGENCY, isShortcutNumber(mLastNumber));
- Bundle extras = new Bundle();
- extras.putInt(TelecomManager.EXTRA_CALL_SOURCE,
- ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD);
- TelecomManager tm = (TelecomManager) getSystemService(TELECOM_SERVICE);
- tm.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, mLastNumber, null), extras);
+ placeCall(mLastNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD,
+ phoneToMakeCall);
} else {
if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
@@ -928,6 +906,20 @@
mDigits.getText().delete(0, mDigits.getText().length());
}
+ private void placeCall(String number, int callSource, ShortcutViewUtils.PhoneInfo phone) {
+ Bundle extras = new Bundle();
+ extras.putInt(TelecomManager.EXTRA_CALL_SOURCE, callSource);
+
+ if (phone != null && phone.getPhoneAccountHandle() != null) {
+ // Requests to dial through the specified phone.
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ phone.getPhoneAccountHandle());
+ }
+
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
+ tm.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null), extras);
+ }
+
/**
* Plays the specified tone for TONE_LENGTH_MS milliseconds.
*
@@ -1071,7 +1063,7 @@
AsyncTask<Void, Void, Boolean> showWfcWarningTask = new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... voids) {
- TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
+ TelephonyManager tm = getSystemService(TelephonyManager.class);
boolean isWfcAvailable = tm.isWifiCallingAvailable();
ServiceState ss = tm.getServiceState();
boolean isCellAvailable =
@@ -1143,14 +1135,15 @@
mEmergencyShortcutButtonList = new ArrayList<>();
setupEmergencyCallShortcutButton();
- updateLocationAndEccInfo(null, null);
+ updateLocationAndEccInfo();
switchView(mEmergencyShortcutView, mDialpadView, false);
}
- private void setLocationInfo(String countryIso) {
+ private void setLocationInfo() {
final View locationInfo = findViewById(R.id.location_info);
+ String countryIso = mPhoneInfo != null ? mPhoneInfo.getCountryIso() : null;
String countryName = null;
if (!TextUtils.isEmpty(countryIso)) {
Locale locale = Locale.getDefault();
@@ -1227,11 +1220,11 @@
mShortcutAdapter.registerDataSetObserver(mShortcutDataSetObserver);
}
- private void updateLocationAndEccInfo(String iso, CountryEccInfo countryEccInfo) {
+ private void updateLocationAndEccInfo() {
if (!isFinishing() && !isDestroyed()) {
- setLocationInfo(iso);
+ setLocationInfo();
if (mShortcutAdapter != null) {
- mShortcutAdapter.updateCountryEccInfo(this, countryEccInfo);
+ mShortcutAdapter.updateCountryEccInfo(this, mPhoneInfo);
}
}
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 80fe71a..ebd69a8 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -265,6 +265,9 @@
private static final String PREF_CARRIERS_SUBSCRIBER_PREFIX = "carrier_subscriber_";
private static final String PREF_PROVISION_IMS_MMTEL_PREFIX = "provision_ims_mmtel_";
+ // String to store multi SIM allowed
+ private static final String PREF_MULTI_SIM_RESTRICTED = "multisim_restricted";
+
// The AID of ISD-R.
private static final String ISDR_AID = "A0000005591010FFFFFFFF8900000100";
@@ -5955,13 +5958,37 @@
}
@Override
- public UiccCardInfo[] getUiccCardsInfo() {
- enforceReadPrivilegedPermission("getUiccCardsInfo");
+ public List<UiccCardInfo> getUiccCardsInfo(String callingPackage) {
+ if (checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
+ != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ throw new SecurityException("Caller does not have carrier privileges on any UICC.");
+ }
final long identity = Binder.clearCallingIdentity();
try {
- ArrayList<UiccCardInfo> cards = UiccController.getInstance().getAllUiccCardInfos();
- return cards.toArray(new UiccCardInfo[cards.size()]);
+ UiccController uiccController = UiccController.getInstance();
+ ArrayList<UiccCardInfo> cardInfos = uiccController.getAllUiccCardInfos();
+
+ ApplicationInfo ai = mApp.getPackageManager().getApplicationInfo(callingPackage, 0);
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ // Remove private info if the caller doesn't have access
+ ArrayList<UiccCardInfo> filteredInfos = new ArrayList<>();
+ for (UiccCardInfo cardInfo : cardInfos) {
+ UiccCard card = uiccController.getUiccCard(cardInfo.getSlotIndex());
+ UiccProfile profile = card.getUiccProfile();
+ if (profile.getCarrierPrivilegeStatus(mApp.getPackageManager(), callingPackage)
+ != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ filteredInfos.add(cardInfo.getUnprivileged());
+ } else {
+ filteredInfos.add(cardInfo);
+ }
+ }
+ return filteredInfos;
+ }
+ return cardInfos;
+ } catch (PackageManager.NameNotFoundException e) {
+ // This should not happen since we pass the package info in from TelephonyManager
+ throw new SecurityException("Invalid calling package.");
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -6041,11 +6068,6 @@
@Override
public int getCardIdForDefaultEuicc(int subId, String callingPackage) {
- if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
- mApp, subId, callingPackage, "getCardIdForDefaultEuicc")) {
- return TelephonyManager.INVALID_CARD_ID;
- }
-
final long identity = Binder.clearCallingIdentity();
try {
return UiccController.getInstance().getCardIdForDefaultEuicc();
@@ -6429,4 +6451,38 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ @Override
+ public void setMultisimCarrierRestriction(boolean isMultisimCarrierRestricted) {
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mTelephonySharedPreferences.edit()
+ .putBoolean(PREF_MULTI_SIM_RESTRICTED, isMultisimCarrierRestricted)
+ .commit();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean isMultisimCarrierRestricted() {
+ enforceReadPrivilegedPermission("isMultisimCarrierRestricted");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // If the device has less than 2 SIM cards, indicate that multisim is restricted.
+ int numPhysicalSlots = UiccController.getInstance().getUiccSlots().length;
+ if (numPhysicalSlots < 2) {
+ loge("isMultisimCarrierRestricted: requires at least 2 cards");
+ return true;
+ }
+
+ // Default value is false. Multi SIM is allowed unless explicitly restricted.
+ return mTelephonySharedPreferences.getBoolean(PREF_MULTI_SIM_RESTRICTED, false);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
diff --git a/src/com/android/phone/ShortcutViewUtils.java b/src/com/android/phone/ShortcutViewUtils.java
new file mode 100644
index 0000000..28ee24f
--- /dev/null
+++ b/src/com/android/phone/ShortcutViewUtils.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.content.Context;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+class ShortcutViewUtils {
+ private static final String LOG_TAG = "ShortcutViewUtils";
+
+ // Emergency services which will be promoted on the shortcut view.
+ static final int[] PROMOTED_CATEGORIES = {
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+ };
+
+ static final int PROMOTED_CATEGORIES_BITMASK;
+
+ static {
+ int bitmask = 0;
+ for (int category : PROMOTED_CATEGORIES) {
+ bitmask |= category;
+ }
+ PROMOTED_CATEGORIES_BITMASK = bitmask;
+ }
+
+ // Info and emergency call capability of every phone.
+ static class PhoneInfo {
+ private final PhoneAccountHandle mHandle;
+ private final boolean mCanPlaceEmergencyCall;
+ private final int mSubId;
+ private final String mCountryIso;
+ private final List<EmergencyNumber> mPromotedEmergencyNumbers;
+
+ private PhoneInfo(int subId, String countryIso,
+ List<EmergencyNumber> promotedEmergencyNumbers) {
+ this(null, true, subId, countryIso, promotedEmergencyNumbers);
+ }
+
+ private PhoneInfo(PhoneAccountHandle handle, boolean canPlaceEmergencyCall, int subId,
+ String countryIso, List<EmergencyNumber> promotedEmergencyNumbers) {
+ mHandle = handle;
+ mCanPlaceEmergencyCall = canPlaceEmergencyCall;
+ mSubId = subId;
+ mCountryIso = countryIso;
+ mPromotedEmergencyNumbers = promotedEmergencyNumbers;
+ }
+
+ public PhoneAccountHandle getPhoneAccountHandle() {
+ return mHandle;
+ }
+
+ public boolean canPlaceEmergencyCall() {
+ return mCanPlaceEmergencyCall;
+ }
+
+ public int getSubId() {
+ return mSubId;
+ }
+
+ public String getCountryIso() {
+ return mCountryIso;
+ }
+
+ public List<EmergencyNumber> getPromotedEmergencyNumbers() {
+ return mPromotedEmergencyNumbers;
+ }
+
+ public boolean isSufficientForEmergencyCall() {
+ // Checking mCountryIso because the emergency number list is not reliable to be
+ // suggested to users if the device didn't camp to any network. In this case, users
+ // can still try to dial emergency numbers with dial pad.
+ return mCanPlaceEmergencyCall && mPromotedEmergencyNumbers != null
+ && !TextUtils.isEmpty(mCountryIso);
+ }
+
+ public boolean hasPromotedEmergencyNumber(String number) {
+ for (EmergencyNumber emergencyNumber : mPromotedEmergencyNumbers) {
+ if (emergencyNumber.getNumber().equalsIgnoreCase(number)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ if (mHandle != null) {
+ sb.append("handle=").append(mHandle.getId()).append(", ");
+ }
+ sb.append("subId=").append(mSubId)
+ .append(", canPlaceEmergencyCall=").append(mCanPlaceEmergencyCall)
+ .append(", networkCountryIso=").append(mCountryIso);
+ if (mPromotedEmergencyNumbers != null) {
+ sb.append(", emergencyNumbers=");
+ for (EmergencyNumber emergencyNumber : mPromotedEmergencyNumbers) {
+ sb.append(emergencyNumber.getNumber()).append(":")
+ .append(emergencyNumber).append(",");
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Picks a preferred phone (SIM slot) which is sufficient for emergency call and can provide
+ * promoted emergency numbers.
+ *
+ * A promoted emergency number should be dialed out over the preferred phone. Other emergency
+ * numbers should be still dialable over the system default phone.
+ *
+ * @return A preferred phone and its promoted emergency number, or null if no phone/promoted
+ * emergency numbers available.
+ */
+ @Nullable
+ static PhoneInfo pickPreferredPhone(@NonNull Context context) {
+ TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+ if (telephonyManager.getPhoneCount() <= 0) {
+ Log.w(LOG_TAG, "No phone available!");
+ return null;
+ }
+
+ Map<Integer, List<EmergencyNumber>> promotedLists =
+ getPromotedEmergencyNumberLists(telephonyManager);
+ if (promotedLists == null || promotedLists.isEmpty()) {
+ return null;
+ }
+
+ // For a multi-phone device, tries the default phone account.
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ PhoneAccountHandle defaultHandle = telecomManager.getDefaultOutgoingPhoneAccount(
+ PhoneAccount.SCHEME_TEL);
+ if (defaultHandle != null) {
+ PhoneInfo phone = loadPhoneInfo(defaultHandle, telephonyManager, telecomManager,
+ promotedLists);
+ if (phone.isSufficientForEmergencyCall()) {
+ return phone;
+ }
+ Log.w(LOG_TAG, "Default PhoneAccount is insufficient for emergency call: "
+ + phone.toString());
+ } else {
+ Log.w(LOG_TAG, "Missing default PhoneAccount! Is this really a phone device?");
+ }
+
+ // Looks for any one phone which supports emergency call.
+ List<PhoneAccountHandle> allHandles = telecomManager.getCallCapablePhoneAccounts();
+ if (allHandles != null && !allHandles.isEmpty()) {
+ for (PhoneAccountHandle handle : allHandles) {
+ PhoneInfo phone = loadPhoneInfo(handle, telephonyManager, telecomManager,
+ promotedLists);
+ if (phone.isSufficientForEmergencyCall()) {
+ return phone;
+ } else {
+ if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
+ Log.d(LOG_TAG, "PhoneAccount " + phone.toString()
+ + " is insufficient for emergency call.");
+ }
+ }
+ }
+ }
+
+ Log.w(LOG_TAG, "No PhoneAccount available for emergency call!");
+ return null;
+ }
+
+ private static PhoneInfo loadPhoneInfo(@NonNull PhoneAccountHandle handle,
+ @NonNull TelephonyManager telephonyManager, @NonNull TelecomManager telecomManager,
+ Map<Integer, List<EmergencyNumber>> promotedLists) {
+ boolean canPlaceEmergencyCall = false;
+ int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ String countryIso = null;
+ List<EmergencyNumber> emergencyNumberList = null;
+
+ PhoneAccount phoneAccount = telecomManager.getPhoneAccount(handle);
+ if (phoneAccount != null) {
+ canPlaceEmergencyCall = phoneAccount.hasCapabilities(
+ PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
+ subId = telephonyManager.getSubIdForPhoneAccount(phoneAccount);
+ }
+
+ TelephonyManager subTelephonyManager = telephonyManager.createForSubscriptionId(subId);
+ if (subTelephonyManager != null) {
+ countryIso = subTelephonyManager.getNetworkCountryIso();
+ }
+
+ if (promotedLists != null) {
+ emergencyNumberList = promotedLists.get(subId);
+ }
+
+ return new PhoneInfo(handle, canPlaceEmergencyCall, subId, countryIso, emergencyNumberList);
+ }
+
+ @NonNull
+ private static Map<Integer, List<EmergencyNumber>> getPromotedEmergencyNumberLists(
+ @NonNull TelephonyManager telephonyManager) {
+ Map<Integer, List<EmergencyNumber>> allLists =
+ telephonyManager.getCurrentEmergencyNumberList();
+ if (allLists == null || allLists.isEmpty()) {
+ Log.w(LOG_TAG, "Unable to retrieve emergency number lists!");
+ return new ArrayMap<>();
+ }
+
+ boolean isDebugLoggable = Log.isLoggable(LOG_TAG, Log.DEBUG);
+ Map<Integer, List<EmergencyNumber>> promotedEmergencyNumberLists = new ArrayMap<>();
+ for (Map.Entry<Integer, List<EmergencyNumber>> entry : allLists.entrySet()) {
+ if (entry.getKey() == null || entry.getValue() == null) {
+ continue;
+ }
+ List<EmergencyNumber> emergencyNumberList = entry.getValue();
+ if (isDebugLoggable) {
+ Log.d(LOG_TAG, "Emergency numbers of " + entry.getKey());
+ }
+
+ // The list of promoted emergency numbers which will be visible on shortcut view.
+ List<EmergencyNumber> promotedList = new ArrayList<>();
+ // A temporary list for non-prioritized emergency numbers.
+ List<EmergencyNumber> tempList = new ArrayList<>();
+
+ for (EmergencyNumber emergencyNumber : emergencyNumberList) {
+ boolean isPromotedCategory = (emergencyNumber.getEmergencyServiceCategoryBitmask()
+ & PROMOTED_CATEGORIES_BITMASK) != 0;
+
+ // Emergency numbers in DATABASE are prioritized for shortcut view since they were
+ // well-categorized.
+ boolean isFromPrioritizedSource =
+ (emergencyNumber.getEmergencyNumberSourceBitmask()
+ & EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE) != 0;
+ if (isDebugLoggable) {
+ Log.d(LOG_TAG, " " + emergencyNumber
+ + (isPromotedCategory ? "M" : "")
+ + (isFromPrioritizedSource ? "P" : ""));
+ }
+
+ if (isPromotedCategory) {
+ if (isFromPrioritizedSource) {
+ promotedList.add(emergencyNumber);
+ } else {
+ tempList.add(emergencyNumber);
+ }
+ }
+ }
+ // Puts numbers in temp list after prioritized numbers.
+ promotedList.addAll(tempList);
+
+ if (!promotedList.isEmpty()) {
+ promotedEmergencyNumberLists.put(entry.getKey(), promotedList);
+ }
+ }
+
+ if (promotedEmergencyNumberLists.isEmpty()) {
+ Log.w(LOG_TAG, "No promoted emergency number found!");
+ }
+ return promotedEmergencyNumberLists;
+ }
+}
diff --git a/src/com/android/phone/ecc/EccInfoHelper.java b/src/com/android/phone/ecc/EccInfoHelper.java
deleted file mode 100644
index c471c4b..0000000
--- a/src/com/android/phone/ecc/EccInfoHelper.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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 com.android.phone.ecc;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.provider.Settings;
-import android.telephony.CellIdentityGsm;
-import android.telephony.CellIdentityLte;
-import android.telephony.CellIdentityWcdma;
-import android.telephony.CellInfo;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellInfoLte;
-import android.telephony.CellInfoWcdma;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.telephony.MccTable;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper for retrieve ECC info for current country.
- */
-public class EccInfoHelper {
- // Debug constants.
- private static final boolean DBG = false;
- private static final String LOG_TAG = "EccInfoHelper";
-
- /**
- * Check if current CountryEccInfo is available for current environment.
- */
- public static boolean isCountryEccInfoAvailable(Context context, String countryIso) {
- CountryEccInfo countryEccInfo;
- try {
- countryEccInfo = IsoToEccProtobufRepository.getInstance()
- .getCountryEccInfo(context, countryIso);
- } catch (IOException e) {
- Log.e(LOG_TAG, "Failed to retrieve ECC: ", e);
- return false;
- }
-
- if (countryEccInfo == null) {
- return false;
- }
- for (EccInfo entry : countryEccInfo.getEccInfoList()) {
- if (!PhoneNumberUtils.isEmergencyNumber(entry.getNumber())) {
- // The CountryEccInfo is unavailable if any ecc number in the local table was
- // declined.
- return false;
- }
- }
- return true;
- }
-
- // country ISO to ECC list data source
- private IsoToEccRepository mEccRepo;
-
- /**
- * Callback for {@link #getCountryEccInfoAsync}.
- */
- public interface CountryEccInfoResultCallback {
- /**
- * Called if successfully get country ECC info.
- *
- * @param iso Detected current country ISO.
- * @param countryEccInfo The EccInfo of current country.
- */
- void onSuccess(@NonNull String iso, @NonNull CountryEccInfo countryEccInfo);
-
- /**
- * Called if failed to get country ISO.
- */
- void onDetectCountryFailed();
-
- /**
- * Called if failed to get ECC info for given country ISO.
- *
- * @param iso Detected current country ISO.
- */
- void onRetrieveCountryEccInfoFailed(@NonNull String iso);
- }
-
- /**
- * Constructor of EccInfoHelper
- *
- * @param eccRepository A repository for ECC info, indexed by country ISO.
- */
- public EccInfoHelper(@NonNull IsoToEccRepository eccRepository) {
- mEccRepo = eccRepository;
- }
-
- /**
- * Get ECC info for current location, base on detected country ISO.
- * It's possible we cannot detect current country, ex. device is in airplane mode,
- * or there's no available base station near by.
- *
- * @param context The context used to access resources.
- * @param callback Callback for result.
- */
- public void getCountryEccInfoAsync(final @NonNull Context context,
- final CountryEccInfoResultCallback callback) {
- new AsyncTask<Void, Void, Pair<String, CountryEccInfo>>() {
- @Override
- protected Pair<String, CountryEccInfo> doInBackground(Void... voids) {
- String iso = getCurrentCountryIso(context);
- if (TextUtils.isEmpty(iso)) {
- return null;
- }
-
- CountryEccInfo dialableCountryEccInfo;
- try {
- // access data source in background thread to avoid possible file IO caused ANR.
- CountryEccInfo rawEccInfo = mEccRepo.getCountryEccInfo(context, iso);
- dialableCountryEccInfo = getDialableCountryEccInfo(rawEccInfo);
- } catch (IOException e) {
- Log.e(LOG_TAG, "Failed to retrieve ECC: " + e.getMessage());
- dialableCountryEccInfo = null;
- }
- return new Pair<>(iso, dialableCountryEccInfo);
- }
-
- @Override
- protected void onPostExecute(Pair<String, CountryEccInfo> result) {
- if (callback != null) {
- if (result == null) {
- callback.onDetectCountryFailed();
- } else {
- String iso = result.first;
- CountryEccInfo countryEccInfo = result.second;
- if (countryEccInfo == null) {
- callback.onRetrieveCountryEccInfoFailed(iso);
- } else {
- callback.onSuccess(iso, countryEccInfo);
- }
- }
- }
- }
- }.execute();
- }
-
- @NonNull
- private CountryEccInfo getDialableCountryEccInfo(CountryEccInfo countryEccInfo) {
- ArrayList<EccInfo> dialableECCList = new ArrayList<>();
- String dialableFallback = null;
-
- // filter out non-dialable ECC
- if (countryEccInfo != null) {
- for (EccInfo entry : countryEccInfo.getEccInfoList()) {
- if (PhoneNumberUtils.isEmergencyNumber(entry.getNumber())) {
- dialableECCList.add(entry);
- }
- }
- String defaultFallback = countryEccInfo.getFallbackEcc();
- if (PhoneNumberUtils.isEmergencyNumber(defaultFallback)) {
- dialableFallback = defaultFallback;
- }
- }
- return new CountryEccInfo(dialableFallback, dialableECCList);
- }
-
- @Nullable
- private String getCurrentCountryIso(@NonNull Context context) {
- // Do not detect country ISO if airplane mode is on
- int airplaneMode = Settings.System.getInt(context.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0);
- if (airplaneMode != 0) {
- Log.d(LOG_TAG, "Airplane mode is on, do not get country ISO.");
- return null;
- }
-
- TelephonyManager tm = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- String iso = tm.getNetworkCountryIso();
- if (DBG) Log.d(LOG_TAG, "Current country ISO is " + Rlog.pii(LOG_TAG, iso));
-
- if (TextUtils.isEmpty(iso)) {
- // XXX: according to ServiceStateTracker's implementation, retrieve cell info in a
- // thread other than TelephonyManager's main thread.
- String mcc = getCurrentMccFromCellInfo(context);
- iso = MccTable.countryCodeForMcc(mcc);
- if (DBG) {
- Log.d(LOG_TAG, "Current mcc is " + Rlog.pii(LOG_TAG, mcc) + ", mapping to ISO: "
- + Rlog.pii(LOG_TAG, iso));
- }
- }
- return iso;
- }
-
- // XXX: According to ServiceStateTracker implementation, to actually get current cell info,
- // this method must be called in a separate thread from ServiceStateTracker, which is the
- // main thread of Telephony service.
- @Nullable
- private String getCurrentMccFromCellInfo(@NonNull Context context) {
- // retrieve mcc info from base station even no SIM present.
- TelephonyManager tm = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- List<CellInfo> cellInfos = tm.getAllCellInfo();
- String mcc = null;
- if (cellInfos != null) {
- for (CellInfo ci : cellInfos) {
- if (ci instanceof CellInfoGsm) {
- CellInfoGsm cellInfoGsm = (CellInfoGsm) ci;
- CellIdentityGsm cellIdentityGsm = cellInfoGsm.getCellIdentity();
- mcc = cellIdentityGsm.getMccString();
- break;
- } else if (ci instanceof CellInfoWcdma) {
- CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ci;
- CellIdentityWcdma cellIdentityWcdma = cellInfoWcdma.getCellIdentity();
- mcc = cellIdentityWcdma.getMccString();
- break;
- } else if (ci instanceof CellInfoLte) {
- CellInfoLte cellInfoLte = (CellInfoLte) ci;
- CellIdentityLte cellIdentityLte = cellInfoLte.getCellIdentity();
- mcc = cellIdentityLte.getMccString();
- break;
- }
- }
- if (DBG) Log.d(LOG_TAG, "Retrieve MCC from cell info list: " + Rlog.pii(LOG_TAG, mcc));
- } else {
- Log.w(LOG_TAG, "Cannot get cell info list.");
- }
- return mcc;
- }
-}
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 37c5d7c..9cc7eb3 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -316,6 +316,8 @@
&& isImsVoiceAvailable()) {
capabilities |= PhoneAccount.CAPABILITY_RTT;
mIsRttCapable = true;
+ } else {
+ mIsRttCapable = false;
}
extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK,
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index ab9e211..e0db44e 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -71,6 +71,8 @@
import java.util.Queue;
import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
/**
* Service for making GSM and CDMA connections.
*/
@@ -321,8 +323,8 @@
if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
// TODO: We don't check for SecurityException here (requires
// CALL_PRIVILEGED permission).
- final Phone phone = getPhoneForAccount(request.getAccountHandle(), false,
- handle.getSchemeSpecificPart());
+ final Phone phone = getPhoneForAccount(request.getAccountHandle(),
+ false /* isEmergencyCall */, null /* not an emergency call */);
if (phone == null) {
Log.d(this, "onCreateOutgoingConnection, phone is null");
return Connection.createFailedConnection(
@@ -360,8 +362,8 @@
"Unable to parse number"));
}
- final Phone phone = getPhoneForAccount(request.getAccountHandle(), false,
- handle.getSchemeSpecificPart());
+ final Phone phone = getPhoneForAccount(request.getAccountHandle(),
+ false /* isEmergencyCall*/, null /* not an emergency call */);
if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
// Obtain the configuration for the outgoing phone's SIM. If the outgoing number
// matches the *228 regex pattern, fail the call. This number is used for OTASP, and
@@ -468,7 +470,8 @@
// Get the right phone object from the account data passed in.
final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
- handle.getSchemeSpecificPart());
+ /* Note: when not an emergency, handle can be null for unknown callers */
+ handle == null ? null : handle.getSchemeSpecificPart());
Connection resultConnection = getTelephonyConnection(request, numberToDial,
isEmergencyNumber, handle, phone);
// If there was a failure, the resulting connection will not be a TelephonyConnection,
@@ -512,8 +515,9 @@
if (isRadioReady) {
// Get the right phone object since the radio has been turned on
// successfully.
- final Phone phone = getPhoneForAccount(request.getAccountHandle(),
- isEmergencyNumber, handle.getSchemeSpecificPart());
+ final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
+ /* Note: when not an emergency, handle can be null for unknown callers */
+ handle == null ? null : handle.getSchemeSpecificPart());
// If the PhoneType of the Phone being used is different than the Default Phone, then we
// need create a new Connection using that PhoneType and replace it in Telecom.
if (phone.getPhoneType() != originalPhoneType) {
@@ -732,7 +736,8 @@
isEmergency = true;
}
Phone phone = getPhoneForAccount(accountHandle, isEmergency,
- request.getAddress().getSchemeSpecificPart());
+ /* Note: when not an emergency, handle can be null for unknown callers */
+ request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
if (phone == null) {
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
@@ -844,7 +849,8 @@
isEmergency = true;
}
Phone phone = getPhoneForAccount(accountHandle, isEmergency,
- request.getAddress().getSchemeSpecificPart());
+ /* Note: when not an emergency, handle can be null for unknown callers */
+ request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
if (phone == null) {
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
@@ -1234,8 +1240,17 @@
return false;
}
+ /**
+ * Determines which {@link Phone} will be used to place the call.
+ * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the
+ * call on.
+ * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise.
+ * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone
+ * of the emergency call. Otherwise, this can be {@code null} .
+ * @return
+ */
private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency,
- String emergencyNumberAddress) {
+ @Nullable String emergencyNumberAddress) {
Phone chosenPhone = null;
int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
diff --git a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
index 708ea66..1cfd3ba 100644
--- a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE"/>
<application android:label="TelephonyRegistryTestApp">
<activity
android:name=".TelephonyRegistryTestApp"
diff --git a/tests/src/com/android/phone/ecc/EccDataTest.java b/tests/src/com/android/phone/ecc/EccDataTest.java
new file mode 100644
index 0000000..8f4abc5
--- /dev/null
+++ b/tests/src/com/android/phone/ecc/EccDataTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.android.phone.ecc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.phone.ecc.nano.ProtobufEccData;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Unit tests for eccdata.
+ */
+@RunWith(AndroidJUnit4.class)
+public class EccDataTest extends TelephonyTestBase {
+ @Test
+ public void testEccDataContent() throws IOException {
+ InputStream eccData = new GZIPInputStream(new BufferedInputStream(
+ InstrumentationRegistry.getTargetContext().getAssets().open("eccdata")));
+ ProtobufEccData.AllInfo allEccMessages = ProtobufEccData.AllInfo.parseFrom(
+ readInputStreamToByteArray(eccData));
+ eccData.close();
+
+ HashSet loadedIsos = new HashSet(300);
+ HashSet loadedNumbers = new HashSet(5);
+
+ for (ProtobufEccData.CountryInfo countryInfo : allEccMessages.countries) {
+ assertThat(countryInfo.isoCode).isNotEmpty();
+ assertThat(countryInfo.isoCode).isEqualTo(countryInfo.isoCode.toUpperCase().trim());
+ assertThat(loadedIsos.contains(countryInfo.isoCode)).isFalse();
+ loadedIsos.add(countryInfo.isoCode);
+
+ loadedNumbers.clear();
+ for (ProtobufEccData.EccInfo eccInfo : countryInfo.eccs) {
+ assertThat(eccInfo.phoneNumber).isNotEmpty();
+ assertThat(eccInfo.phoneNumber).isEqualTo(eccInfo.phoneNumber.trim());
+ assertThat(loadedNumbers.contains(eccInfo.phoneNumber)).isFalse();
+ assertThat(eccInfo.types).isNotEmpty();
+ loadedNumbers.add(eccInfo.phoneNumber);
+ }
+ }
+ }
+
+ /**
+ * Util function to convert inputStream to byte array before parsing proto data.
+ */
+ private static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ int nRead;
+ byte[] data = new byte[16 * 1024]; // Read 16k chunks
+ while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ }
+ buffer.flush();
+ return buffer.toByteArray();
+ }
+}
diff --git a/tests/src/com/android/phone/ecc/IsoToEccProtobufRepositoryTest.java b/tests/src/com/android/phone/ecc/IsoToEccProtobufRepositoryTest.java
deleted file mode 100644
index f6e5ba2..0000000
--- a/tests/src/com/android/phone/ecc/IsoToEccProtobufRepositoryTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 com.android.phone.ecc;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-
-import com.android.TelephonyTestBase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashSet;
-import java.util.Map;
-
-/**
- * Unit tests for IsoToEccProtobufRepository.
- */
-
-@RunWith(AndroidJUnit4.class)
-public class IsoToEccProtobufRepositoryTest extends TelephonyTestBase {
- private static final String LOG_TAG = "IsoToEccProtobufRepositoryTest";
-
- @Test
- public void testEccDataContent() {
- IsoToEccProtobufRepository repository = IsoToEccProtobufRepository.getInstance();
- repository.loadMappingTable(InstrumentationRegistry.getTargetContext());
- Map<String, CountryEccInfo> eccTable = repository.getEccTable();
- HashSet loadedIsos = new HashSet(300);
- HashSet loadedNumbers = new HashSet(5);
-
- assertThat(eccTable).isNotEmpty();
- for (Map.Entry<String, CountryEccInfo> entry : eccTable.entrySet()) {
- String countryIso = entry.getKey();
- CountryEccInfo countryEccInfo = entry.getValue();
- EccInfo[] eccInfoList = countryEccInfo.getEccInfoList();
- if (eccInfoList.length > 0) {
- Log.i(LOG_TAG, "Verifying country " + countryIso + " with "
- + eccInfoList.length + " ecc(s)");
- } else {
- Log.w(LOG_TAG, "Verifying country " + countryIso + " with no ecc");
- }
-
- assertThat(countryIso).isNotEmpty();
- assertThat(countryIso).isEqualTo(countryIso.toUpperCase().trim());
- assertThat(loadedIsos.contains(countryIso)).isFalse();
- loadedIsos.add(countryIso);
-
- assertThat(countryEccInfo.getFallbackEcc()).isNotEmpty();
-
- if (eccInfoList.length != 0) {
- loadedNumbers.clear();
- for (EccInfo eccInfo : eccInfoList) {
- String eccNumber = eccInfo.getNumber();
- assertThat(eccNumber).isNotEmpty();
- assertThat(eccNumber).isEqualTo(eccNumber.trim());
- assertThat(eccInfo.getTypes()).isNotEmpty();
- assertThat(loadedNumbers.contains(eccNumber)).isFalse();
- loadedNumbers.add(eccNumber);
- }
- }
- }
- }
-}