Loading proto/src/telephony.proto +34 −2 Original line number Original line Diff line number Diff line Loading @@ -1608,11 +1608,43 @@ message TelephonyEvent { // Group id level 1. Logged only if gid1 is configured from subscription // Group id level 1. Logged only if gid1 is configured from subscription // but its matching rule is unknown // but its matching rule is unknown optional string gid1 = 2; optional string unknown_gid1 = 2; // MCC and MNC that map to this carrier. Logged only if mccmnc is configured // MCC and MNC that map to this carrier. Logged only if mccmnc is configured // from subscription but its matching rule is unknown // from subscription but its matching rule is unknown optional string mccmnc = 3; optional string unknown_mccmnc = 3; // MCC and MNC from the subscription that map to this carrier. optional string mccmnc = 4; // Group id level 1 from the subscription that map to this carrier. optional string gid1 = 5; // Group id level 2 from the subscription that map to this carrier. optional string gid2 = 6; // spn from the subscription that map to this carrier. optional string spn = 7; // pnn from the subscription that map to this carrier. optional string pnn = 8; // iccid prefix from the subscription that map to this carrier. // only log first 7 outof 20 bit of full iccid optional string iccid_prefix = 9; // imsi prefix from the subscription that map to this carrier. // only log additional 2 bits other than MCC MNC. optional string imsi_prefix = 10; // Carrier Privilege Access Rule in hex string from the subscription. // Sample values: 61ed377e85d386a8dfee6b864bd85b0bfaa5af88 repeated string privilege_access_rule = 11; // The Access Point Name, corresponding to "apn" field returned by // "content://telephony/carriers/preferapn" on device. // Sample values: fast.t-mobile.com, internet. Note only log if this apn is not user edited. optional string preferApn = 12; } } // Time when event happened on device, in milliseconds since epoch // Time when event happened on device, in milliseconds since epoch Loading src/java/com/android/internal/telephony/AppSmsManager.java +148 −7 Original line number Original line Diff line number Diff line Loading @@ -16,25 +16,39 @@ package com.android.internal.telephony; package com.android.internal.telephony; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.app.PendingIntent; import android.app.role.IRoleManager; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.os.Binder; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.provider.Telephony.Sms.Intents; import android.provider.Telephony.Sms.Intents; import android.telephony.IFinancialSmsCallback; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.telephony.SmsMessage; import android.telephony.SubscriptionManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.Base64; import android.util.Base64; import android.util.Log; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.security.SecureRandom; import java.security.SecureRandom; import java.util.Map; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** /** * Manager for app specific incoming SMS requests. This can be used to implement SMS based * Manager for app specific SMS requests. This can be used to implement SMS based * communication channels (e.g. for SMS based phone number verification) without needing the * communication channels (e.g. for SMS based phone number verification) without needing the * {@link Manifest.permission#RECEIVE_SMS} permission. * {@link Manifest.permission#RECEIVE_SMS} permission. * * Loading @@ -44,6 +58,7 @@ import java.util.Map; public class AppSmsManager { public class AppSmsManager { private static final String LOG_TAG = "AppSmsManager"; private static final String LOG_TAG = "AppSmsManager"; private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5); private final SecureRandom mRandom; private final SecureRandom mRandom; private final Context mContext; private final Context mContext; private final Object mLock = new Object(); private final Object mLock = new Object(); Loading Loading @@ -91,6 +106,64 @@ public class AppSmsManager { return token; return token; } } /** * Create an app specific incoming SMS request for the the calling package. * * This method returns a token that if included in a subsequent incoming SMS message the * {@link Intents.SMS_RECEIVED_ACTION} intent will be delivered only to the calling package and * will not require the application have the {@link Manifest.permission#RECEIVE_SMS} permission. * * An app can only have one request at a time, if the app already has a request it will be * dropped and the new one will be added. * * @return Token to include in an SMS to have it delivered directly to the app. */ public String createAppSpecificSmsTokenWithPackageInfo(int subId, @NonNull String callingPackageName, @Nullable String prefixes, @NonNull PendingIntent intent) { Preconditions.checkStringNotEmpty(callingPackageName, "callingPackageName cannot be null or empty."); Preconditions.checkNotNull(intent, "intent cannot be null"); // Check calling uid matches callingpkg. AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); appOps.checkPackage(Binder.getCallingUid(), callingPackageName); // Generate a token to store the request under. String token = PackageBasedTokenUtil.generateToken(mContext, callingPackageName); if (token != null) { synchronized (mLock) { // Only allow one request in flight from a package. if (mPackageMap.containsKey(callingPackageName)) { removeRequestLocked(mPackageMap.get(callingPackageName)); } // Store state. AppRequestInfo info = new AppRequestInfo( callingPackageName, intent, token, prefixes, subId, true); addRequestLocked(info); } } return token; } /** * Get filtered SMS messages for financial app. */ public void getSmsMessagesForFinancialApp( String callingPkg, Bundle params, final IFinancialSmsCallback callback) { try { IRoleManager roleManager = IRoleManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.ROLE_SERVICE)); roleManager.getSmsMessagesForFinancialApp(callingPkg, params, callback); } catch (RemoteException e) { Log.e(LOG_TAG, "Receive RemoteException."); // do nothing } catch (ServiceNotFoundException e) { Log.e(LOG_TAG, "Service not found."); // do nothing } } /** /** * Handle an incoming SMS_DELIVER_ACTION intent if it is an app-only SMS. * Handle an incoming SMS_DELIVER_ACTION intent if it is an app-only SMS. */ */ Loading @@ -102,14 +175,27 @@ public class AppSmsManager { } } synchronized (mLock) { synchronized (mLock) { AppRequestInfo info = findAppRequestInfoSmsIntentLocked(intent); removeExpiredTokenLocked(); String message = extractMessage(intent); if (TextUtils.isEmpty(message)) { return false; } AppRequestInfo info = findAppRequestInfoSmsIntentLocked(message); if (info == null) { if (info == null) { // The message didn't contain a token -- nothing to do. // The message didn't contain a token -- nothing to do. return false; return false; } } try { try { Intent fillIn = new Intent(); Intent fillIn = new Intent() fillIn.putExtras(intent.getExtras()); .putExtras(intent.getExtras()) .putExtra(SmsManager.EXTRA_STATUS, SmsManager.RESULT_STATUS_SUCCESS) .putExtra(SmsManager.EXTRA_SMS_MESSAGE, message) .putExtra(SmsManager.EXTRA_SIM_SUBSCRIPTION_ID, info.subId) .addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); info.pendingIntent.send(mContext, 0, fillIn); info.pendingIntent.send(mContext, 0, fillIn); } catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) { // The pending intent is canceled, send this SMS as normal. // The pending intent is canceled, send this SMS as normal. Loading @@ -122,7 +208,31 @@ public class AppSmsManager { } } } } private AppRequestInfo findAppRequestInfoSmsIntentLocked(Intent intent) { private void removeExpiredTokenLocked() { final long currentTimeMillis = System.currentTimeMillis(); final Set<String> keySet = mTokenMap.keySet(); for (String token : keySet) { AppRequestInfo request = mTokenMap.get(token); if (request.packageBasedToken && (currentTimeMillis - TIMEOUT_MILLIS > request.timestamp)) { // Send the provided intent with SMS retriever status try { Intent fillIn = new Intent() .putExtra(SmsManager.EXTRA_STATUS, SmsManager.RESULT_STATUS_TIMEOUT) .addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); request.pendingIntent.send(mContext, 0, fillIn); } catch (PendingIntent.CanceledException e) { // do nothing } removeRequestLocked(request); } } } private String extractMessage(Intent intent) { SmsMessage[] messages = Intents.getMessagesFromIntent(intent); SmsMessage[] messages = Intents.getMessagesFromIntent(intent); if (messages == null) { if (messages == null) { return null; return null; Loading @@ -135,11 +245,13 @@ public class AppSmsManager { fullMessageBuilder.append(message.getMessageBody()); fullMessageBuilder.append(message.getMessageBody()); } } String fullMessage = fullMessageBuilder.toString(); return fullMessageBuilder.toString(); } private AppRequestInfo findAppRequestInfoSmsIntentLocked(String fullMessage) { // Look for any tokens in the full message. // Look for any tokens in the full message. for (String token : mTokenMap.keySet()) { for (String token : mTokenMap.keySet()) { if (fullMessage.contains(token)) { if (fullMessage.trim().contains(token) && hasPrefix(token, fullMessage)) { return mTokenMap.get(token); return mTokenMap.get(token); } } } } Loading @@ -152,6 +264,21 @@ public class AppSmsManager { return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING); return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING); } } private boolean hasPrefix(String token, String message) { AppRequestInfo request = mTokenMap.get(token); if (TextUtils.isEmpty(request.prefixes)) { return true; } String[] prefixes = request.prefixes.split(SmsManager.REGEX_PREFIX_DELIMITER); for (String prefix : prefixes) { if (message.startsWith(prefix)) { return true; } } return false; } private void removeRequestLocked(AppRequestInfo info) { private void removeRequestLocked(AppRequestInfo info) { mTokenMap.remove(info.token); mTokenMap.remove(info.token); mPackageMap.remove(info.packageName); mPackageMap.remove(info.packageName); Loading @@ -166,11 +293,25 @@ public class AppSmsManager { public final String packageName; public final String packageName; public final PendingIntent pendingIntent; public final PendingIntent pendingIntent; public final String token; public final String token; public final long timestamp; public final String prefixes; public final int subId; public final boolean packageBasedToken; AppRequestInfo(String packageName, PendingIntent pendingIntent, String token) { AppRequestInfo(String packageName, PendingIntent pendingIntent, String token) { this(packageName, pendingIntent, token, null, SubscriptionManager.INVALID_SUBSCRIPTION_ID, false); } AppRequestInfo(String packageName, PendingIntent pendingIntent, String token, String prefixes, int subId, boolean packageBasedToken) { this.packageName = packageName; this.packageName = packageName; this.pendingIntent = pendingIntent; this.pendingIntent = pendingIntent; this.token = token; this.token = token; this.timestamp = System.currentTimeMillis(); this.prefixes = prefixes; this.subId = subId; this.packageBasedToken = packageBasedToken; } } } } Loading src/java/com/android/internal/telephony/BaseCommands.java +2 −3 Original line number Original line Diff line number Diff line Loading @@ -846,12 +846,11 @@ public abstract class BaseCommands implements CommandsInterface { * * * RadioState has 3 values : RADIO_OFF, RADIO_UNAVAILABLE, RADIO_ON. * RadioState has 3 values : RADIO_OFF, RADIO_UNAVAILABLE, RADIO_ON. * * * @param newState new radio power state decoded from RIL_UNSOL_RADIO_STATE_CHANGED * @param newState new RadioState decoded from RIL_UNSOL_RADIO_STATE_CHANGED * @param forceNotifyRegistrants boolean indicating if registrants should be notified even if * @param forceNotifyRegistrants boolean indicating if registrants should be notified even if * there is no change in state * there is no change in state */ */ protected void setRadioState(@TelephonyManager.RadioPowerState int newState, protected void setRadioState(int newState, boolean forceNotifyRegistrants) { boolean forceNotifyRegistrants) { int oldState; int oldState; synchronized (mStateMonitor) { synchronized (mStateMonitor) { Loading src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java +48 −20 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.internal.telephony; package com.android.internal.telephony; import static android.preference.PreferenceManager.getDefaultSharedPreferences; import static android.preference.PreferenceManager.getDefaultSharedPreferences; import static android.telephony.CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8; Loading @@ -30,6 +31,8 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences; import android.database.Cursor; import android.database.Cursor; import android.net.Uri; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.os.PersistableBundle; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; import android.telephony.CarrierConfigManager; import android.telephony.ImsiEncryptionInfo; import android.telephony.ImsiEncryptionInfo; Loading Loading @@ -64,7 +67,7 @@ import java.util.zip.GZIPInputStream; * This class contains logic to get Certificates and keep them current. * This class contains logic to get Certificates and keep them current. * The class will be instantiated by various Phone implementations. * The class will be instantiated by various Phone implementations. */ */ public class CarrierKeyDownloadManager { public class CarrierKeyDownloadManager extends Handler { private static final String LOG_TAG = "CarrierKeyDownloadManager"; private static final String LOG_TAG = "CarrierKeyDownloadManager"; private static final String MCC_MNC_PREF_TAG = "CARRIER_KEY_DM_MCC_MNC"; private static final String MCC_MNC_PREF_TAG = "CARRIER_KEY_DM_MCC_MNC"; Loading Loading @@ -93,8 +96,6 @@ public class CarrierKeyDownloadManager { private static final String SEPARATOR = ":"; private static final String SEPARATOR = ":"; private static final String JSON_CERTIFICATE = "certificate"; private static final String JSON_CERTIFICATE = "certificate"; // This is a hack to accommodate certain Carriers who insists on using the public-key // field to store the certificate. We'll just use which-ever is not null. private static final String JSON_CERTIFICATE_ALTERNATE = "public-key"; private static final String JSON_CERTIFICATE_ALTERNATE = "public-key"; private static final String JSON_TYPE = "key-type"; private static final String JSON_TYPE = "key-type"; private static final String JSON_IDENTIFIER = "key-identifier"; private static final String JSON_IDENTIFIER = "key-identifier"; Loading @@ -102,15 +103,18 @@ public class CarrierKeyDownloadManager { private static final String JSON_TYPE_VALUE_WLAN = "WLAN"; private static final String JSON_TYPE_VALUE_WLAN = "WLAN"; private static final String JSON_TYPE_VALUE_EPDG = "EPDG"; private static final String JSON_TYPE_VALUE_EPDG = "EPDG"; private static final int EVENT_ALARM_OR_CONFIG_CHANGE = 0; private static final int EVENT_DOWNLOAD_COMPLETE = 1; private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG, private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG, TelephonyManager.KEY_TYPE_WLAN}; TelephonyManager.KEY_TYPE_WLAN}; private static final int UNINITIALIZED_KEY_TYPE = -1; private final Phone mPhone; private final Phone mPhone; private final Context mContext; private final Context mContext; public final DownloadManager mDownloadManager; public final DownloadManager mDownloadManager; private String mURL; private String mURL; private boolean mAllowedOverMeteredNetwork = false; public CarrierKeyDownloadManager(Phone phone) { public CarrierKeyDownloadManager(Phone phone) { mPhone = phone; mPhone = phone; Loading @@ -131,31 +135,43 @@ public class CarrierKeyDownloadManager { int slotId = mPhone.getPhoneId(); int slotId = mPhone.getPhoneId(); if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId)) { if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId)) { Log.d(LOG_TAG, "Handling key renewal alarm: " + action); Log.d(LOG_TAG, "Handling key renewal alarm: " + action); handleAlarmOrConfigChange(); sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) { } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) { if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { Log.d(LOG_TAG, "Handling reset intent: " + action); Log.d(LOG_TAG, "Handling reset intent: " + action); handleAlarmOrConfigChange(); sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); } } } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { Log.d(LOG_TAG, "Carrier Config changed: " + action); Log.d(LOG_TAG, "Carrier Config changed: " + action); handleAlarmOrConfigChange(); sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); } } } else if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { } else if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { Log.d(LOG_TAG, "Download Complete"); Log.d(LOG_TAG, "Download Complete"); long carrierKeyDownloadIdentifier = sendMessage(obtainMessage(EVENT_DOWNLOAD_COMPLETE, intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0); intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0))); } } }; @Override public void handleMessage (Message msg) { switch (msg.what) { case EVENT_ALARM_OR_CONFIG_CHANGE: handleAlarmOrConfigChange(); break; case EVENT_DOWNLOAD_COMPLETE: long carrierKeyDownloadIdentifier = (long) msg.obj; String mccMnc = getMccMncSetFromPref(); String mccMnc = getMccMncSetFromPref(); if (isValidDownload(mccMnc)) { if (isValidDownload(mccMnc)) { onDownloadComplete(carrierKeyDownloadIdentifier, mccMnc); onDownloadComplete(carrierKeyDownloadIdentifier, mccMnc); onPostDownloadProcessing(carrierKeyDownloadIdentifier); onPostDownloadProcessing(carrierKeyDownloadIdentifier); } } break; } } } } }; private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) { private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) { resetRenewalAlarm(); resetRenewalAlarm(); Loading Loading @@ -370,6 +386,8 @@ public class CarrierKeyDownloadManager { } } mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING); mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING); mAllowedOverMeteredNetwork = b.getBoolean( KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL); if (TextUtils.isEmpty(mURL) || mKeyAvailability == 0) { if (TextUtils.isEmpty(mURL) || mKeyAvailability == 0) { Log.d(LOG_TAG, "Carrier not enabled or invalid values"); Log.d(LOG_TAG, "Carrier not enabled or invalid values"); return false; return false; Loading Loading @@ -430,20 +448,27 @@ public class CarrierKeyDownloadManager { JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS); JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS); for (int i = 0; i < keys.length(); i++) { for (int i = 0; i < keys.length(); i++) { JSONObject key = keys.getJSONObject(i); JSONObject key = keys.getJSONObject(i); // This is a hack to accommodate certain carriers who insist on using the public-key // Support both "public-key" and "certificate" String property. // field to store the certificate. We'll just use which-ever is not null. // "certificate" is a more accurate description, however, the 3GPP draft spec // S3-170116, "Privacy Protection for EAP-AKA" section 4.3 mandates the use of // "public-key". String cert = null; String cert = null; if (key.has(JSON_CERTIFICATE)) { if (key.has(JSON_CERTIFICATE)) { cert = key.getString(JSON_CERTIFICATE); cert = key.getString(JSON_CERTIFICATE); } else { } else { cert = key.getString(JSON_CERTIFICATE_ALTERNATE); cert = key.getString(JSON_CERTIFICATE_ALTERNATE); } } // The 3GPP draft spec 3GPP draft spec S3-170116, "Privacy Protection for EAP-AKA" // section 4.3, does not specify any key-type property. To be compatible with these // networks, the logic defaults to WLAN type if not specified. int type = TelephonyManager.KEY_TYPE_WLAN; if (key.has(JSON_TYPE)) { String typeString = key.getString(JSON_TYPE); String typeString = key.getString(JSON_TYPE); int type = UNINITIALIZED_KEY_TYPE; if (typeString.equals(JSON_TYPE_VALUE_EPDG)) { if (typeString.equals(JSON_TYPE_VALUE_WLAN)) { type = TelephonyManager.KEY_TYPE_WLAN; } else if (typeString.equals(JSON_TYPE_VALUE_EPDG)) { type = TelephonyManager.KEY_TYPE_EPDG; type = TelephonyManager.KEY_TYPE_EPDG; } else if (!typeString.equals(JSON_TYPE_VALUE_WLAN)) { Log.e(LOG_TAG, "Invalid key-type specified: " + typeString); } } } String identifier = key.getString(JSON_IDENTIFIER); String identifier = key.getString(JSON_IDENTIFIER); ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes()); ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes()); Loading Loading @@ -520,7 +545,10 @@ public class CarrierKeyDownloadManager { } } try { try { DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL)); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL)); request.setAllowedOverMetered(false); // TODO(b/128550341): Implement the logic to minimize using metered network such as // LTE for downloading a certificate. request.setAllowedOverMetered(mAllowedOverMeteredNetwork); request.setVisibleInDownloadsUi(false); request.setVisibleInDownloadsUi(false); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request); Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request); Loading src/java/com/android/internal/telephony/CarrierResolver.java +187 −103 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
proto/src/telephony.proto +34 −2 Original line number Original line Diff line number Diff line Loading @@ -1608,11 +1608,43 @@ message TelephonyEvent { // Group id level 1. Logged only if gid1 is configured from subscription // Group id level 1. Logged only if gid1 is configured from subscription // but its matching rule is unknown // but its matching rule is unknown optional string gid1 = 2; optional string unknown_gid1 = 2; // MCC and MNC that map to this carrier. Logged only if mccmnc is configured // MCC and MNC that map to this carrier. Logged only if mccmnc is configured // from subscription but its matching rule is unknown // from subscription but its matching rule is unknown optional string mccmnc = 3; optional string unknown_mccmnc = 3; // MCC and MNC from the subscription that map to this carrier. optional string mccmnc = 4; // Group id level 1 from the subscription that map to this carrier. optional string gid1 = 5; // Group id level 2 from the subscription that map to this carrier. optional string gid2 = 6; // spn from the subscription that map to this carrier. optional string spn = 7; // pnn from the subscription that map to this carrier. optional string pnn = 8; // iccid prefix from the subscription that map to this carrier. // only log first 7 outof 20 bit of full iccid optional string iccid_prefix = 9; // imsi prefix from the subscription that map to this carrier. // only log additional 2 bits other than MCC MNC. optional string imsi_prefix = 10; // Carrier Privilege Access Rule in hex string from the subscription. // Sample values: 61ed377e85d386a8dfee6b864bd85b0bfaa5af88 repeated string privilege_access_rule = 11; // The Access Point Name, corresponding to "apn" field returned by // "content://telephony/carriers/preferapn" on device. // Sample values: fast.t-mobile.com, internet. Note only log if this apn is not user edited. optional string preferApn = 12; } } // Time when event happened on device, in milliseconds since epoch // Time when event happened on device, in milliseconds since epoch Loading
src/java/com/android/internal/telephony/AppSmsManager.java +148 −7 Original line number Original line Diff line number Diff line Loading @@ -16,25 +16,39 @@ package com.android.internal.telephony; package com.android.internal.telephony; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.app.PendingIntent; import android.app.role.IRoleManager; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.os.Binder; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.provider.Telephony.Sms.Intents; import android.provider.Telephony.Sms.Intents; import android.telephony.IFinancialSmsCallback; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.telephony.SmsMessage; import android.telephony.SubscriptionManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.Base64; import android.util.Base64; import android.util.Log; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.security.SecureRandom; import java.security.SecureRandom; import java.util.Map; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** /** * Manager for app specific incoming SMS requests. This can be used to implement SMS based * Manager for app specific SMS requests. This can be used to implement SMS based * communication channels (e.g. for SMS based phone number verification) without needing the * communication channels (e.g. for SMS based phone number verification) without needing the * {@link Manifest.permission#RECEIVE_SMS} permission. * {@link Manifest.permission#RECEIVE_SMS} permission. * * Loading @@ -44,6 +58,7 @@ import java.util.Map; public class AppSmsManager { public class AppSmsManager { private static final String LOG_TAG = "AppSmsManager"; private static final String LOG_TAG = "AppSmsManager"; private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5); private final SecureRandom mRandom; private final SecureRandom mRandom; private final Context mContext; private final Context mContext; private final Object mLock = new Object(); private final Object mLock = new Object(); Loading Loading @@ -91,6 +106,64 @@ public class AppSmsManager { return token; return token; } } /** * Create an app specific incoming SMS request for the the calling package. * * This method returns a token that if included in a subsequent incoming SMS message the * {@link Intents.SMS_RECEIVED_ACTION} intent will be delivered only to the calling package and * will not require the application have the {@link Manifest.permission#RECEIVE_SMS} permission. * * An app can only have one request at a time, if the app already has a request it will be * dropped and the new one will be added. * * @return Token to include in an SMS to have it delivered directly to the app. */ public String createAppSpecificSmsTokenWithPackageInfo(int subId, @NonNull String callingPackageName, @Nullable String prefixes, @NonNull PendingIntent intent) { Preconditions.checkStringNotEmpty(callingPackageName, "callingPackageName cannot be null or empty."); Preconditions.checkNotNull(intent, "intent cannot be null"); // Check calling uid matches callingpkg. AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); appOps.checkPackage(Binder.getCallingUid(), callingPackageName); // Generate a token to store the request under. String token = PackageBasedTokenUtil.generateToken(mContext, callingPackageName); if (token != null) { synchronized (mLock) { // Only allow one request in flight from a package. if (mPackageMap.containsKey(callingPackageName)) { removeRequestLocked(mPackageMap.get(callingPackageName)); } // Store state. AppRequestInfo info = new AppRequestInfo( callingPackageName, intent, token, prefixes, subId, true); addRequestLocked(info); } } return token; } /** * Get filtered SMS messages for financial app. */ public void getSmsMessagesForFinancialApp( String callingPkg, Bundle params, final IFinancialSmsCallback callback) { try { IRoleManager roleManager = IRoleManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.ROLE_SERVICE)); roleManager.getSmsMessagesForFinancialApp(callingPkg, params, callback); } catch (RemoteException e) { Log.e(LOG_TAG, "Receive RemoteException."); // do nothing } catch (ServiceNotFoundException e) { Log.e(LOG_TAG, "Service not found."); // do nothing } } /** /** * Handle an incoming SMS_DELIVER_ACTION intent if it is an app-only SMS. * Handle an incoming SMS_DELIVER_ACTION intent if it is an app-only SMS. */ */ Loading @@ -102,14 +175,27 @@ public class AppSmsManager { } } synchronized (mLock) { synchronized (mLock) { AppRequestInfo info = findAppRequestInfoSmsIntentLocked(intent); removeExpiredTokenLocked(); String message = extractMessage(intent); if (TextUtils.isEmpty(message)) { return false; } AppRequestInfo info = findAppRequestInfoSmsIntentLocked(message); if (info == null) { if (info == null) { // The message didn't contain a token -- nothing to do. // The message didn't contain a token -- nothing to do. return false; return false; } } try { try { Intent fillIn = new Intent(); Intent fillIn = new Intent() fillIn.putExtras(intent.getExtras()); .putExtras(intent.getExtras()) .putExtra(SmsManager.EXTRA_STATUS, SmsManager.RESULT_STATUS_SUCCESS) .putExtra(SmsManager.EXTRA_SMS_MESSAGE, message) .putExtra(SmsManager.EXTRA_SIM_SUBSCRIPTION_ID, info.subId) .addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); info.pendingIntent.send(mContext, 0, fillIn); info.pendingIntent.send(mContext, 0, fillIn); } catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) { // The pending intent is canceled, send this SMS as normal. // The pending intent is canceled, send this SMS as normal. Loading @@ -122,7 +208,31 @@ public class AppSmsManager { } } } } private AppRequestInfo findAppRequestInfoSmsIntentLocked(Intent intent) { private void removeExpiredTokenLocked() { final long currentTimeMillis = System.currentTimeMillis(); final Set<String> keySet = mTokenMap.keySet(); for (String token : keySet) { AppRequestInfo request = mTokenMap.get(token); if (request.packageBasedToken && (currentTimeMillis - TIMEOUT_MILLIS > request.timestamp)) { // Send the provided intent with SMS retriever status try { Intent fillIn = new Intent() .putExtra(SmsManager.EXTRA_STATUS, SmsManager.RESULT_STATUS_TIMEOUT) .addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); request.pendingIntent.send(mContext, 0, fillIn); } catch (PendingIntent.CanceledException e) { // do nothing } removeRequestLocked(request); } } } private String extractMessage(Intent intent) { SmsMessage[] messages = Intents.getMessagesFromIntent(intent); SmsMessage[] messages = Intents.getMessagesFromIntent(intent); if (messages == null) { if (messages == null) { return null; return null; Loading @@ -135,11 +245,13 @@ public class AppSmsManager { fullMessageBuilder.append(message.getMessageBody()); fullMessageBuilder.append(message.getMessageBody()); } } String fullMessage = fullMessageBuilder.toString(); return fullMessageBuilder.toString(); } private AppRequestInfo findAppRequestInfoSmsIntentLocked(String fullMessage) { // Look for any tokens in the full message. // Look for any tokens in the full message. for (String token : mTokenMap.keySet()) { for (String token : mTokenMap.keySet()) { if (fullMessage.contains(token)) { if (fullMessage.trim().contains(token) && hasPrefix(token, fullMessage)) { return mTokenMap.get(token); return mTokenMap.get(token); } } } } Loading @@ -152,6 +264,21 @@ public class AppSmsManager { return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING); return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING); } } private boolean hasPrefix(String token, String message) { AppRequestInfo request = mTokenMap.get(token); if (TextUtils.isEmpty(request.prefixes)) { return true; } String[] prefixes = request.prefixes.split(SmsManager.REGEX_PREFIX_DELIMITER); for (String prefix : prefixes) { if (message.startsWith(prefix)) { return true; } } return false; } private void removeRequestLocked(AppRequestInfo info) { private void removeRequestLocked(AppRequestInfo info) { mTokenMap.remove(info.token); mTokenMap.remove(info.token); mPackageMap.remove(info.packageName); mPackageMap.remove(info.packageName); Loading @@ -166,11 +293,25 @@ public class AppSmsManager { public final String packageName; public final String packageName; public final PendingIntent pendingIntent; public final PendingIntent pendingIntent; public final String token; public final String token; public final long timestamp; public final String prefixes; public final int subId; public final boolean packageBasedToken; AppRequestInfo(String packageName, PendingIntent pendingIntent, String token) { AppRequestInfo(String packageName, PendingIntent pendingIntent, String token) { this(packageName, pendingIntent, token, null, SubscriptionManager.INVALID_SUBSCRIPTION_ID, false); } AppRequestInfo(String packageName, PendingIntent pendingIntent, String token, String prefixes, int subId, boolean packageBasedToken) { this.packageName = packageName; this.packageName = packageName; this.pendingIntent = pendingIntent; this.pendingIntent = pendingIntent; this.token = token; this.token = token; this.timestamp = System.currentTimeMillis(); this.prefixes = prefixes; this.subId = subId; this.packageBasedToken = packageBasedToken; } } } } Loading
src/java/com/android/internal/telephony/BaseCommands.java +2 −3 Original line number Original line Diff line number Diff line Loading @@ -846,12 +846,11 @@ public abstract class BaseCommands implements CommandsInterface { * * * RadioState has 3 values : RADIO_OFF, RADIO_UNAVAILABLE, RADIO_ON. * RadioState has 3 values : RADIO_OFF, RADIO_UNAVAILABLE, RADIO_ON. * * * @param newState new radio power state decoded from RIL_UNSOL_RADIO_STATE_CHANGED * @param newState new RadioState decoded from RIL_UNSOL_RADIO_STATE_CHANGED * @param forceNotifyRegistrants boolean indicating if registrants should be notified even if * @param forceNotifyRegistrants boolean indicating if registrants should be notified even if * there is no change in state * there is no change in state */ */ protected void setRadioState(@TelephonyManager.RadioPowerState int newState, protected void setRadioState(int newState, boolean forceNotifyRegistrants) { boolean forceNotifyRegistrants) { int oldState; int oldState; synchronized (mStateMonitor) { synchronized (mStateMonitor) { Loading
src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java +48 −20 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.internal.telephony; package com.android.internal.telephony; import static android.preference.PreferenceManager.getDefaultSharedPreferences; import static android.preference.PreferenceManager.getDefaultSharedPreferences; import static android.telephony.CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8; Loading @@ -30,6 +31,8 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences; import android.database.Cursor; import android.database.Cursor; import android.net.Uri; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.os.PersistableBundle; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; import android.telephony.CarrierConfigManager; import android.telephony.ImsiEncryptionInfo; import android.telephony.ImsiEncryptionInfo; Loading Loading @@ -64,7 +67,7 @@ import java.util.zip.GZIPInputStream; * This class contains logic to get Certificates and keep them current. * This class contains logic to get Certificates and keep them current. * The class will be instantiated by various Phone implementations. * The class will be instantiated by various Phone implementations. */ */ public class CarrierKeyDownloadManager { public class CarrierKeyDownloadManager extends Handler { private static final String LOG_TAG = "CarrierKeyDownloadManager"; private static final String LOG_TAG = "CarrierKeyDownloadManager"; private static final String MCC_MNC_PREF_TAG = "CARRIER_KEY_DM_MCC_MNC"; private static final String MCC_MNC_PREF_TAG = "CARRIER_KEY_DM_MCC_MNC"; Loading Loading @@ -93,8 +96,6 @@ public class CarrierKeyDownloadManager { private static final String SEPARATOR = ":"; private static final String SEPARATOR = ":"; private static final String JSON_CERTIFICATE = "certificate"; private static final String JSON_CERTIFICATE = "certificate"; // This is a hack to accommodate certain Carriers who insists on using the public-key // field to store the certificate. We'll just use which-ever is not null. private static final String JSON_CERTIFICATE_ALTERNATE = "public-key"; private static final String JSON_CERTIFICATE_ALTERNATE = "public-key"; private static final String JSON_TYPE = "key-type"; private static final String JSON_TYPE = "key-type"; private static final String JSON_IDENTIFIER = "key-identifier"; private static final String JSON_IDENTIFIER = "key-identifier"; Loading @@ -102,15 +103,18 @@ public class CarrierKeyDownloadManager { private static final String JSON_TYPE_VALUE_WLAN = "WLAN"; private static final String JSON_TYPE_VALUE_WLAN = "WLAN"; private static final String JSON_TYPE_VALUE_EPDG = "EPDG"; private static final String JSON_TYPE_VALUE_EPDG = "EPDG"; private static final int EVENT_ALARM_OR_CONFIG_CHANGE = 0; private static final int EVENT_DOWNLOAD_COMPLETE = 1; private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG, private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG, TelephonyManager.KEY_TYPE_WLAN}; TelephonyManager.KEY_TYPE_WLAN}; private static final int UNINITIALIZED_KEY_TYPE = -1; private final Phone mPhone; private final Phone mPhone; private final Context mContext; private final Context mContext; public final DownloadManager mDownloadManager; public final DownloadManager mDownloadManager; private String mURL; private String mURL; private boolean mAllowedOverMeteredNetwork = false; public CarrierKeyDownloadManager(Phone phone) { public CarrierKeyDownloadManager(Phone phone) { mPhone = phone; mPhone = phone; Loading @@ -131,31 +135,43 @@ public class CarrierKeyDownloadManager { int slotId = mPhone.getPhoneId(); int slotId = mPhone.getPhoneId(); if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId)) { if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId)) { Log.d(LOG_TAG, "Handling key renewal alarm: " + action); Log.d(LOG_TAG, "Handling key renewal alarm: " + action); handleAlarmOrConfigChange(); sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) { } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) { if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { Log.d(LOG_TAG, "Handling reset intent: " + action); Log.d(LOG_TAG, "Handling reset intent: " + action); handleAlarmOrConfigChange(); sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); } } } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY, SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { Log.d(LOG_TAG, "Carrier Config changed: " + action); Log.d(LOG_TAG, "Carrier Config changed: " + action); handleAlarmOrConfigChange(); sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); } } } else if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { } else if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { Log.d(LOG_TAG, "Download Complete"); Log.d(LOG_TAG, "Download Complete"); long carrierKeyDownloadIdentifier = sendMessage(obtainMessage(EVENT_DOWNLOAD_COMPLETE, intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0); intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0))); } } }; @Override public void handleMessage (Message msg) { switch (msg.what) { case EVENT_ALARM_OR_CONFIG_CHANGE: handleAlarmOrConfigChange(); break; case EVENT_DOWNLOAD_COMPLETE: long carrierKeyDownloadIdentifier = (long) msg.obj; String mccMnc = getMccMncSetFromPref(); String mccMnc = getMccMncSetFromPref(); if (isValidDownload(mccMnc)) { if (isValidDownload(mccMnc)) { onDownloadComplete(carrierKeyDownloadIdentifier, mccMnc); onDownloadComplete(carrierKeyDownloadIdentifier, mccMnc); onPostDownloadProcessing(carrierKeyDownloadIdentifier); onPostDownloadProcessing(carrierKeyDownloadIdentifier); } } break; } } } } }; private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) { private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) { resetRenewalAlarm(); resetRenewalAlarm(); Loading Loading @@ -370,6 +386,8 @@ public class CarrierKeyDownloadManager { } } mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING); mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING); mAllowedOverMeteredNetwork = b.getBoolean( KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL); if (TextUtils.isEmpty(mURL) || mKeyAvailability == 0) { if (TextUtils.isEmpty(mURL) || mKeyAvailability == 0) { Log.d(LOG_TAG, "Carrier not enabled or invalid values"); Log.d(LOG_TAG, "Carrier not enabled or invalid values"); return false; return false; Loading Loading @@ -430,20 +448,27 @@ public class CarrierKeyDownloadManager { JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS); JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS); for (int i = 0; i < keys.length(); i++) { for (int i = 0; i < keys.length(); i++) { JSONObject key = keys.getJSONObject(i); JSONObject key = keys.getJSONObject(i); // This is a hack to accommodate certain carriers who insist on using the public-key // Support both "public-key" and "certificate" String property. // field to store the certificate. We'll just use which-ever is not null. // "certificate" is a more accurate description, however, the 3GPP draft spec // S3-170116, "Privacy Protection for EAP-AKA" section 4.3 mandates the use of // "public-key". String cert = null; String cert = null; if (key.has(JSON_CERTIFICATE)) { if (key.has(JSON_CERTIFICATE)) { cert = key.getString(JSON_CERTIFICATE); cert = key.getString(JSON_CERTIFICATE); } else { } else { cert = key.getString(JSON_CERTIFICATE_ALTERNATE); cert = key.getString(JSON_CERTIFICATE_ALTERNATE); } } // The 3GPP draft spec 3GPP draft spec S3-170116, "Privacy Protection for EAP-AKA" // section 4.3, does not specify any key-type property. To be compatible with these // networks, the logic defaults to WLAN type if not specified. int type = TelephonyManager.KEY_TYPE_WLAN; if (key.has(JSON_TYPE)) { String typeString = key.getString(JSON_TYPE); String typeString = key.getString(JSON_TYPE); int type = UNINITIALIZED_KEY_TYPE; if (typeString.equals(JSON_TYPE_VALUE_EPDG)) { if (typeString.equals(JSON_TYPE_VALUE_WLAN)) { type = TelephonyManager.KEY_TYPE_WLAN; } else if (typeString.equals(JSON_TYPE_VALUE_EPDG)) { type = TelephonyManager.KEY_TYPE_EPDG; type = TelephonyManager.KEY_TYPE_EPDG; } else if (!typeString.equals(JSON_TYPE_VALUE_WLAN)) { Log.e(LOG_TAG, "Invalid key-type specified: " + typeString); } } } String identifier = key.getString(JSON_IDENTIFIER); String identifier = key.getString(JSON_IDENTIFIER); ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes()); ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes()); Loading Loading @@ -520,7 +545,10 @@ public class CarrierKeyDownloadManager { } } try { try { DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL)); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL)); request.setAllowedOverMetered(false); // TODO(b/128550341): Implement the logic to minimize using metered network such as // LTE for downloading a certificate. request.setAllowedOverMetered(mAllowedOverMeteredNetwork); request.setVisibleInDownloadsUi(false); request.setVisibleInDownloadsUi(false); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request); Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request); Loading
src/java/com/android/internal/telephony/CarrierResolver.java +187 −103 File changed.Preview size limit exceeded, changes collapsed. Show changes