1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ims.rcs.uce.presence.publish; 18 19 import android.content.Context; 20 import android.net.Uri; 21 import android.telecom.PhoneAccount; 22 import android.telephony.PhoneNumberUtils; 23 import android.telephony.TelephonyManager; 24 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities; 25 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import com.android.i18n.phonenumbers.NumberParseException; 30 import com.android.i18n.phonenumbers.PhoneNumberUtil; 31 import com.android.i18n.phonenumbers.Phonenumber; 32 import com.android.ims.rcs.uce.util.UceUtils; 33 34 import java.util.Arrays; 35 36 /** 37 * The util class of publishing device's capabilities. 38 */ 39 public class PublishUtils { 40 private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishUtils"; 41 42 private static final String SCHEME_SIP = "sip"; 43 private static final String SCHEME_TEL = "tel"; 44 private static final String DOMAIN_SEPARATOR = "@"; 45 46 /** 47 * @return the contact URI of this device for either a PRESENCE or OPTIONS capabilities request. 48 * We will first try to use the IMS service associated URIs from the p-associated-uri header 49 * in the IMS registration response. If this is not available, we will fall back to using the 50 * SIM card information to generate the URI. 51 */ getDeviceContactUri(Context context, int subId, DeviceCapabilityInfo deviceCap, boolean isForPresence)52 public static Uri getDeviceContactUri(Context context, int subId, 53 DeviceCapabilityInfo deviceCap, boolean isForPresence) { 54 boolean preferTelUri = false; 55 if (isForPresence) { 56 preferTelUri = UceUtils.isTelUriForPidfXmlEnabled(context, subId); 57 } 58 // Get the uri from the IMS p-associated-uri header which is provided by the IMS service. 59 Uri contactUri = deviceCap.getImsAssociatedUri(preferTelUri); 60 if (contactUri != null) { 61 Uri convertedUri = preferTelUri ? getConvertedTelUri(context, contactUri) : contactUri; 62 Log.d(LOG_TAG, "getDeviceContactUri: returning " 63 + (contactUri.equals(convertedUri) ? "found" : "converted") 64 + " ims associated uri"); 65 return contactUri; 66 } 67 68 // No IMS service provided URIs, so generate the contact uri from ISIM. 69 TelephonyManager telephonyManager = getTelephonyManager(context, subId); 70 if (telephonyManager == null) { 71 Log.w(LOG_TAG, "getDeviceContactUri: TelephonyManager is null"); 72 return null; 73 } 74 contactUri = getContactUriFromIsim(telephonyManager); 75 if (contactUri != null) { 76 Log.d(LOG_TAG, "getDeviceContactUri: impu"); 77 if (preferTelUri) { 78 return getConvertedTelUri(context, contactUri); 79 } else { 80 return contactUri; 81 } 82 } else { 83 Log.d(LOG_TAG, "getDeviceContactUri: line number"); 84 if (preferTelUri) { 85 return getConvertedTelUri(context, getContactUriFromLine1Number(telephonyManager)); 86 } else { 87 return getContactUriFromLine1Number(telephonyManager); 88 } 89 } 90 } 91 92 /** 93 * Find all instances of sip/sips/tel URIs containing PII and replace them. 94 * <p> 95 * This is used for removing PII in logging. 96 * @param source The source string to remove the phone numbers from. 97 * @return A version of the given string with SIP URIs removed. 98 */ removeNumbersFromUris(String source)99 public static String removeNumbersFromUris(String source) { 100 // Replace only the number portion in the sip/sips/tel URI 101 return source.replaceAll("(?:sips?|tel):(\\+?[\\d\\-]+)", "[removed]"); 102 } 103 getContactUriFromIsim(TelephonyManager telephonyManager)104 private static Uri getContactUriFromIsim(TelephonyManager telephonyManager) { 105 // Get the home network domain and the array of the public user identities 106 String domain = telephonyManager.getIsimDomain(); 107 String[] impus = telephonyManager.getIsimImpu(); 108 109 if (TextUtils.isEmpty(domain) || impus == null) { 110 Log.d(LOG_TAG, "getContactUriFromIsim: domain is null=" + TextUtils.isEmpty(domain)); 111 Log.d(LOG_TAG, "getContactUriFromIsim: impu is null=" + 112 ((impus == null || impus.length == 0) ? "true" : "false")); 113 return null; 114 } 115 116 for (String impu : impus) { 117 if (TextUtils.isEmpty(impu)) continue; 118 Uri impuUri = Uri.parse(impu); 119 String scheme = impuUri.getScheme(); 120 String schemeSpecificPart = impuUri.getSchemeSpecificPart(); 121 if (SCHEME_SIP.equals(scheme) && !TextUtils.isEmpty(schemeSpecificPart) && 122 schemeSpecificPart.endsWith(domain)) { 123 return impuUri; 124 } 125 } 126 Log.d(LOG_TAG, "getContactUriFromIsim: there is no impu matching the domain"); 127 return null; 128 } 129 getContactUriFromLine1Number(TelephonyManager telephonyManager)130 private static Uri getContactUriFromLine1Number(TelephonyManager telephonyManager) { 131 String phoneNumber = formatPhoneNumber(telephonyManager.getLine1Number()); 132 if (TextUtils.isEmpty(phoneNumber)) { 133 Log.w(LOG_TAG, "Cannot get the phone number"); 134 return null; 135 } 136 137 String domain = telephonyManager.getIsimDomain(); 138 if (!TextUtils.isEmpty(domain)) { 139 return Uri.fromParts(SCHEME_SIP, phoneNumber + DOMAIN_SEPARATOR + domain, null); 140 } else { 141 return Uri.fromParts(SCHEME_TEL, phoneNumber, null); 142 } 143 } 144 formatPhoneNumber(final String phoneNumber)145 private static String formatPhoneNumber(final String phoneNumber) { 146 if (TextUtils.isEmpty(phoneNumber)) { 147 Log.w(LOG_TAG, "formatPhoneNumber: phone number is empty"); 148 return null; 149 } 150 String number = PhoneNumberUtils.stripSeparators(phoneNumber); 151 return PhoneNumberUtils.normalizeNumber(number); 152 } 153 getTelephonyManager(Context context, int subId)154 private static TelephonyManager getTelephonyManager(Context context, int subId) { 155 TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); 156 if (telephonyManager == null) { 157 return null; 158 } else { 159 return telephonyManager.createForSubscriptionId(subId); 160 } 161 } 162 163 /** 164 * @return a TEL URI version of the contact URI if given a SIP URI. If given a TEL URI, this 165 * method will return the same value given. 166 */ getConvertedTelUri(Context context, Uri contactUri)167 private static Uri getConvertedTelUri(Context context, Uri contactUri) { 168 if (contactUri == null) { 169 return null; 170 } 171 if (contactUri.getScheme().equalsIgnoreCase(SCHEME_SIP)) { 172 TelephonyManager manager = context.getSystemService(TelephonyManager.class); 173 if (manager.getIsimDomain() == null) { 174 return contactUri; 175 } 176 177 String numbers = contactUri.getSchemeSpecificPart(); 178 String[] numberParts = numbers.split("[@;:]"); 179 String number = numberParts[0]; 180 181 String simCountryIso = manager.getSimCountryIso(); 182 if (!TextUtils.isEmpty(simCountryIso)) { 183 simCountryIso = simCountryIso.toUpperCase(); 184 PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 185 try { 186 Phonenumber.PhoneNumber phoneNumber = util.parse(number, simCountryIso); 187 number = util.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164); 188 String telUri = SCHEME_TEL + ":" + number; 189 contactUri = Uri.parse(telUri); 190 } catch (NumberParseException e) { 191 Log.w(LOG_TAG, "formatNumber: could not format " + number + ", error: " + e); 192 } 193 } 194 } 195 return contactUri; 196 } 197 getCapabilityType(Context context, int subId)198 static @RcsImsCapabilityFlag int getCapabilityType(Context context, int subId) { 199 boolean isPresenceSupported = UceUtils.isPresenceSupported(context, subId); 200 boolean isSipOptionsSupported = UceUtils.isSipOptionsSupported(context, subId); 201 if (isPresenceSupported) { 202 return RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE; 203 } else if (isSipOptionsSupported) { 204 return RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE; 205 } else { 206 // Return NONE when neither OPTIONS nor PRESENCE is supported. 207 return RcsImsCapabilities.CAPABILITY_TYPE_NONE; 208 } 209 } 210 } 211