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.internal.telephony.metrics; 18 19 import static com.android.internal.telephony.InboundSmsHandler.SOURCE_INJECTED_FROM_IMS; 20 import static com.android.internal.telephony.InboundSmsHandler.SOURCE_INJECTED_FROM_UNKNOWN; 21 import static com.android.internal.telephony.InboundSmsHandler.SOURCE_NOT_INJECTED; 22 import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE; 23 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_GENERIC; 24 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_NOT_SUPPORTED; 25 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_NO_MEMORY; 26 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_SUCCESS; 27 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP; 28 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2; 29 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP; 30 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2; 31 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_IMS; 32 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_UNKNOWN; 33 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL; 34 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP; 35 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_VOICEMAIL_INDICATION; 36 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_WAP_PUSH; 37 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_ZERO; 38 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR; 39 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK; 40 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY; 41 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS; 42 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_UNKNOWN; 43 44 import android.annotation.Nullable; 45 import android.app.Activity; 46 import android.provider.Telephony.Sms.Intents; 47 import android.telephony.Annotation.NetworkType; 48 import android.telephony.ServiceState; 49 import android.telephony.SmsManager; 50 import android.telephony.TelephonyManager; 51 import android.telephony.ims.stub.ImsRegistrationImplBase; 52 import android.telephony.ims.stub.ImsSmsImplBase; 53 import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult; 54 55 import com.android.internal.telephony.InboundSmsHandler; 56 import com.android.internal.telephony.Phone; 57 import com.android.internal.telephony.PhoneConstants; 58 import com.android.internal.telephony.PhoneFactory; 59 import com.android.internal.telephony.ServiceStateTracker; 60 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms; 61 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms; 62 import com.android.telephony.Rlog; 63 64 import java.util.Objects; 65 import java.util.Random; 66 67 /** Collects sms events per phone ID for the pulled atom. */ 68 public class SmsStats { 69 private static final String TAG = SmsStats.class.getSimpleName(); 70 71 /** 3GPP error for out of service: "no network service" in TS 27.005 cl 3.2.5 */ 72 private static final int NO_NETWORK_ERROR_3GPP = 331; 73 74 /** 3GPP2 error for out of service: "Other radio interface problem" in N.S0005 Table 171 */ 75 private static final int NO_NETWORK_ERROR_3GPP2 = 66; 76 77 private final Phone mPhone; 78 79 private final PersistAtomsStorage mAtomsStorage = 80 PhoneFactory.getMetricsCollector().getAtomsStorage(); 81 82 private static final Random RANDOM = new Random(); 83 SmsStats(Phone phone)84 public SmsStats(Phone phone) { 85 mPhone = phone; 86 } 87 88 /** Create a new atom when multi-part incoming SMS is dropped due to missing parts. */ onDroppedIncomingMultipartSms(boolean is3gpp2, int receivedCount, int totalCount)89 public void onDroppedIncomingMultipartSms(boolean is3gpp2, int receivedCount, int totalCount) { 90 IncomingSms proto = getIncomingDefaultProto(is3gpp2, SOURCE_NOT_INJECTED); 91 // Keep SMS tech as unknown because it's possible that it changed overtime and is not 92 // necessarily the current one. Similarly mark the RAT as unknown. 93 proto.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_UNKNOWN; 94 proto.rat = TelephonyManager.NETWORK_TYPE_UNKNOWN; 95 proto.error = INCOMING_SMS__ERROR__SMS_ERROR_GENERIC; 96 proto.totalParts = totalCount; 97 proto.receivedParts = receivedCount; 98 mAtomsStorage.addIncomingSms(proto); 99 } 100 101 /** Create a new atom when an SMS for the voicemail indicator is received. */ onIncomingSmsVoicemail(boolean is3gpp2, @InboundSmsHandler.SmsSource int smsSource)102 public void onIncomingSmsVoicemail(boolean is3gpp2, 103 @InboundSmsHandler.SmsSource int smsSource) { 104 IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource); 105 proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_VOICEMAIL_INDICATION; 106 mAtomsStorage.addIncomingSms(proto); 107 } 108 109 /** Create a new atom when an SMS of type zero is received. */ onIncomingSmsTypeZero(@nboundSmsHandler.SmsSource int smsSource)110 public void onIncomingSmsTypeZero(@InboundSmsHandler.SmsSource int smsSource) { 111 IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource); 112 proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_ZERO; 113 mAtomsStorage.addIncomingSms(proto); 114 } 115 116 /** Create a new atom when an SMS-PP for the SIM card is received. */ onIncomingSmsPP(@nboundSmsHandler.SmsSource int smsSource, boolean success)117 public void onIncomingSmsPP(@InboundSmsHandler.SmsSource int smsSource, boolean success) { 118 IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource); 119 proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP; 120 proto.error = getIncomingSmsError(success); 121 mAtomsStorage.addIncomingSms(proto); 122 } 123 124 /** Create a new atom when an SMS is received successfully. */ onIncomingSmsSuccess(boolean is3gpp2, @InboundSmsHandler.SmsSource int smsSource, int messageCount, boolean blocked, long messageId)125 public void onIncomingSmsSuccess(boolean is3gpp2, 126 @InboundSmsHandler.SmsSource int smsSource, int messageCount, 127 boolean blocked, long messageId) { 128 IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource); 129 proto.totalParts = messageCount; 130 proto.receivedParts = messageCount; 131 proto.blocked = blocked; 132 proto.messageId = messageId; 133 mAtomsStorage.addIncomingSms(proto); 134 } 135 136 /** Create a new atom when an incoming SMS has an error. */ onIncomingSmsError(boolean is3gpp2, @InboundSmsHandler.SmsSource int smsSource, int result)137 public void onIncomingSmsError(boolean is3gpp2, 138 @InboundSmsHandler.SmsSource int smsSource, int result) { 139 IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource); 140 proto.error = getIncomingSmsError(result); 141 mAtomsStorage.addIncomingSms(proto); 142 } 143 144 /** Create a new atom when an incoming WAP_PUSH SMS is received. */ onIncomingSmsWapPush(@nboundSmsHandler.SmsSource int smsSource, int messageCount, int result, long messageId)145 public void onIncomingSmsWapPush(@InboundSmsHandler.SmsSource int smsSource, 146 int messageCount, int result, long messageId) { 147 IncomingSms proto = getIncomingDefaultProto(false, smsSource); 148 proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_WAP_PUSH; 149 proto.totalParts = messageCount; 150 proto.receivedParts = messageCount; 151 proto.error = getIncomingSmsError(result); 152 proto.messageId = messageId; 153 mAtomsStorage.addIncomingSms(proto); 154 } 155 156 /** Create a new atom when an outgoing SMS is sent. */ onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs, @SmsManager.Result int errorCode, long messageId, boolean isFromDefaultApp, long intervalMillis)157 public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs, 158 @SmsManager.Result int errorCode, long messageId, boolean isFromDefaultApp, 159 long intervalMillis) { 160 onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, errorCode, NO_ERROR_CODE, 161 messageId, isFromDefaultApp, intervalMillis); 162 } 163 164 /** Create a new atom when an outgoing SMS is sent. */ onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs, @SmsManager.Result int errorCode, int radioSpecificErrorCode, long messageId, boolean isFromDefaultApp, long intervalMillis)165 public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs, 166 @SmsManager.Result int errorCode, int radioSpecificErrorCode, long messageId, 167 boolean isFromDefaultApp, long intervalMillis) { 168 OutgoingSms proto = 169 getOutgoingDefaultProto(is3gpp2, isOverIms, messageId, isFromDefaultApp, 170 intervalMillis); 171 172 if (isOverIms) { 173 // Populate error code and result for IMS case 174 proto.errorCode = errorCode; 175 if (fallbackToCs) { 176 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK; 177 } else if (errorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) { 178 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY; 179 } else if (errorCode != SmsManager.RESULT_ERROR_NONE) { 180 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR; 181 } 182 } else { 183 // Populate error code and result for CS case 184 if (errorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) { 185 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY; 186 } else if (errorCode != SmsManager.RESULT_ERROR_NONE) { 187 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR; 188 } 189 proto.errorCode = radioSpecificErrorCode; 190 if (errorCode == SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE 191 && radioSpecificErrorCode == NO_ERROR_CODE) { 192 proto.errorCode = is3gpp2 ? NO_NETWORK_ERROR_3GPP2 : NO_NETWORK_ERROR_3GPP; 193 } 194 } 195 mAtomsStorage.addOutgoingSms(proto); 196 } 197 198 /** Creates a proto for a normal single-part {@code IncomingSms} with default values. */ getIncomingDefaultProto(boolean is3gpp2, @InboundSmsHandler.SmsSource int smsSource)199 private IncomingSms getIncomingDefaultProto(boolean is3gpp2, 200 @InboundSmsHandler.SmsSource int smsSource) { 201 IncomingSms proto = new IncomingSms(); 202 proto.smsFormat = getSmsFormat(is3gpp2); 203 proto.smsTech = getSmsTech(smsSource, is3gpp2); 204 proto.rat = getRat(smsSource); 205 proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL; 206 proto.totalParts = 1; 207 proto.receivedParts = 1; 208 proto.blocked = false; 209 proto.error = INCOMING_SMS__ERROR__SMS_SUCCESS; 210 proto.isRoaming = getIsRoaming(); 211 proto.simSlotIndex = getPhoneId(); 212 proto.isMultiSim = SimSlotState.isMultiSim(); 213 proto.isEsim = SimSlotState.isEsim(getPhoneId()); 214 proto.carrierId = getCarrierId(); 215 // Message ID is initialized with random number, as it is not available for all incoming 216 // SMS messages (e.g. those handled by OS or error cases). 217 proto.messageId = RANDOM.nextLong(); 218 proto.count = 1; 219 return proto; 220 } 221 222 /** Create a proto for a normal {@code OutgoingSms} with default values. */ getOutgoingDefaultProto(boolean is3gpp2, boolean isOverIms, long messageId, boolean isFromDefaultApp, long intervalMillis)223 private OutgoingSms getOutgoingDefaultProto(boolean is3gpp2, boolean isOverIms, 224 long messageId, boolean isFromDefaultApp, long intervalMillis) { 225 OutgoingSms proto = new OutgoingSms(); 226 proto.smsFormat = getSmsFormat(is3gpp2); 227 proto.smsTech = getSmsTech(isOverIms, is3gpp2); 228 proto.rat = getRat(isOverIms); 229 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS; 230 proto.errorCode = isOverIms ? SmsManager.RESULT_ERROR_NONE : NO_ERROR_CODE; 231 proto.isRoaming = getIsRoaming(); 232 proto.isFromDefaultApp = isFromDefaultApp; 233 proto.simSlotIndex = getPhoneId(); 234 proto.isMultiSim = SimSlotState.isMultiSim(); 235 proto.isEsim = SimSlotState.isEsim(getPhoneId()); 236 proto.carrierId = getCarrierId(); 237 // If the message ID is invalid, generate a random value 238 proto.messageId = messageId != 0L ? messageId : RANDOM.nextLong(); 239 // Setting the retry ID to zero. If needed, it will be incremented when the atom is added 240 // in the persistent storage. 241 proto.retryId = 0; 242 proto.intervalMillis = intervalMillis; 243 proto.count = 1; 244 return proto; 245 } 246 getSmsFormat(boolean is3gpp2)247 private static int getSmsFormat(boolean is3gpp2) { 248 if (is3gpp2) { 249 return INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2; 250 } else { 251 return INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP; 252 } 253 } 254 getSmsTech(@nboundSmsHandler.SmsSource int smsSource, boolean is3gpp2)255 private int getSmsTech(@InboundSmsHandler.SmsSource int smsSource, boolean is3gpp2) { 256 if (smsSource == SOURCE_INJECTED_FROM_UNKNOWN) { 257 return INCOMING_SMS__SMS_TECH__SMS_TECH_UNKNOWN; 258 } 259 return getSmsTech(smsSource == SOURCE_INJECTED_FROM_IMS, is3gpp2); 260 } 261 getSmsTech(boolean isOverIms, boolean is3gpp2)262 private int getSmsTech(boolean isOverIms, boolean is3gpp2) { 263 if (isOverIms) { 264 return INCOMING_SMS__SMS_TECH__SMS_TECH_IMS; 265 } else if (is3gpp2) { 266 return INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2; 267 } else { 268 return INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP; 269 } 270 } 271 getIncomingSmsError(int result)272 private static int getIncomingSmsError(int result) { 273 switch (result) { 274 case Activity.RESULT_OK: 275 case Intents.RESULT_SMS_HANDLED: 276 return INCOMING_SMS__ERROR__SMS_SUCCESS; 277 case Intents.RESULT_SMS_OUT_OF_MEMORY: 278 return INCOMING_SMS__ERROR__SMS_ERROR_NO_MEMORY; 279 case Intents.RESULT_SMS_UNSUPPORTED: 280 return INCOMING_SMS__ERROR__SMS_ERROR_NOT_SUPPORTED; 281 case Intents.RESULT_SMS_GENERIC_ERROR: 282 default: 283 return INCOMING_SMS__ERROR__SMS_ERROR_GENERIC; 284 } 285 } 286 getIncomingSmsError(boolean success)287 private static int getIncomingSmsError(boolean success) { 288 if (success) { 289 return INCOMING_SMS__ERROR__SMS_SUCCESS; 290 } else { 291 return INCOMING_SMS__ERROR__SMS_ERROR_GENERIC; 292 } 293 } 294 getOutgoingSmsError(@endStatusResult int imsSendResult)295 private static int getOutgoingSmsError(@SendStatusResult int imsSendResult) { 296 switch (imsSendResult) { 297 case ImsSmsImplBase.SEND_STATUS_OK: 298 return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS; 299 case ImsSmsImplBase.SEND_STATUS_ERROR: 300 return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR; 301 case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY: 302 return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY; 303 case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK: 304 return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK; 305 default: 306 return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_UNKNOWN; 307 } 308 } 309 310 /** 311 * Returns a hash value to identify messages that are identical for the purpose of merging them 312 * together when storage is full. 313 */ getSmsHashCode(OutgoingSms sms)314 static int getSmsHashCode(OutgoingSms sms) { 315 return Objects.hash(sms.smsFormat, sms.smsTech, sms.rat, sms.sendResult, sms.errorCode, 316 sms.isRoaming, sms.isFromDefaultApp, sms.simSlotIndex, sms.isMultiSim, 317 sms.isEsim, sms.carrierId); 318 } 319 320 /** 321 * Returns a hash value to identify messages that are identical for the purpose of merging them 322 * together when storage is full. 323 */ getSmsHashCode(IncomingSms sms)324 static int getSmsHashCode(IncomingSms sms) { 325 return Objects.hash(sms.smsFormat, sms.smsTech, sms.rat, sms.smsType, 326 sms.totalParts, sms.receivedParts, sms.blocked, sms.error, 327 sms.isRoaming, sms.simSlotIndex, sms.isMultiSim, sms.isEsim, sms.carrierId); 328 } 329 getPhoneId()330 private int getPhoneId() { 331 Phone phone = mPhone; 332 if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 333 phone = mPhone.getDefaultPhone(); 334 } 335 return phone.getPhoneId(); 336 } 337 338 @Nullable getServiceState()339 private ServiceState getServiceState() { 340 Phone phone = mPhone; 341 if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 342 phone = mPhone.getDefaultPhone(); 343 } 344 ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker(); 345 return serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 346 } 347 getRat(@nboundSmsHandler.SmsSource int smsSource)348 private @NetworkType int getRat(@InboundSmsHandler.SmsSource int smsSource) { 349 if (smsSource == SOURCE_INJECTED_FROM_UNKNOWN) { 350 return TelephonyManager.NETWORK_TYPE_UNKNOWN; 351 } 352 return getRat(smsSource == SOURCE_INJECTED_FROM_IMS); 353 } 354 getRat(boolean isOverIms)355 private @NetworkType int getRat(boolean isOverIms) { 356 if (isOverIms) { 357 if (mPhone.getImsRegistrationTech() 358 == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) { 359 return TelephonyManager.NETWORK_TYPE_IWLAN; 360 } 361 } 362 // TODO(b/168837897): Returns the RAT at the time the SMS was received.. 363 ServiceState serviceState = getServiceState(); 364 return serviceState != null 365 ? serviceState.getVoiceNetworkType() : TelephonyManager.NETWORK_TYPE_UNKNOWN; 366 } 367 getIsRoaming()368 private boolean getIsRoaming() { 369 ServiceState serviceState = getServiceState(); 370 return serviceState != null ? serviceState.getRoaming() : false; 371 } 372 getCarrierId()373 private int getCarrierId() { 374 Phone phone = mPhone; 375 if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 376 phone = mPhone.getDefaultPhone(); 377 } 378 return phone.getCarrierId(); 379 } 380 loge(String format, Object... args)381 private void loge(String format, Object... args) { 382 Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 383 } 384 } 385