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; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.os.PersistableBundle; 26 import android.telecom.PhoneAccount; 27 import android.telecom.PhoneAccountHandle; 28 import android.telecom.TelecomManager; 29 import android.telephony.CarrierConfigManager; 30 import android.telephony.Rlog; 31 import android.telephony.SmsMessage; 32 import android.text.TextUtils; 33 34 import java.time.Instant; 35 import java.time.LocalDateTime; 36 import java.time.ZoneId; 37 import java.time.format.DateTimeFormatter; 38 import java.util.Arrays; 39 import java.util.regex.Matcher; 40 import java.util.regex.Pattern; 41 import java.util.regex.PatternSyntaxException; 42 43 /** 44 * The SMS filter for parsing SMS from carrier to notify users about the missed incoming call. 45 */ 46 public class MissedIncomingCallSmsFilter { 47 private static final String TAG = MissedIncomingCallSmsFilter.class.getSimpleName(); 48 49 private static final boolean VDBG = false; // STOPSHIP if true 50 51 private static final String SMS_YEAR_TAG = "year"; 52 53 private static final String SMS_MONTH_TAG = "month"; 54 55 private static final String SMS_DAY_TAG = "day"; 56 57 private static final String SMS_HOUR_TAG = "hour"; 58 59 private static final String SMS_MINUTE_TAG = "minute"; 60 61 private static final String SMS_CALLER_ID_TAG = "callerId"; 62 63 private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT = 64 new ComponentName("com.android.phone", 65 "com.android.services.telephony.TelephonyConnectionService"); 66 67 private final Phone mPhone; 68 69 private PersistableBundle mCarrierConfig; 70 71 /** 72 * Constructor 73 * 74 * @param phone The phone instance 75 */ MissedIncomingCallSmsFilter(Phone phone)76 public MissedIncomingCallSmsFilter(Phone phone) { 77 mPhone = phone; 78 79 CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext() 80 .getSystemService(Context.CARRIER_CONFIG_SERVICE); 81 if (configManager != null) { 82 mCarrierConfig = configManager.getConfigForSubId(mPhone.getSubId()); 83 } 84 } 85 86 /** 87 * Check if the message is missed incoming call SMS, which is sent from the carrier to notify 88 * the user about the missed incoming call earlier. 89 * 90 * @param pdus SMS pdu binary 91 * @param format Either {@link SmsConstants#FORMAT_3GPP} or {@link SmsConstants#FORMAT_3GPP2} 92 * @return {@code true} if this is an SMS for notifying the user about missed incoming call. 93 */ filter(byte[][] pdus, String format)94 public boolean filter(byte[][] pdus, String format) { 95 // The missed incoming call SMS must be one page only, and if not we should ignore it. 96 if (pdus.length != 1) { 97 return false; 98 } 99 100 if (mCarrierConfig != null) { 101 String[] originators = mCarrierConfig.getStringArray(CarrierConfigManager 102 .KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY); 103 if (originators != null) { 104 SmsMessage message = SmsMessage.createFromPdu(pdus[0], format); 105 if (message != null 106 && !TextUtils.isEmpty(message.getOriginatingAddress()) 107 && Arrays.asList(originators).contains(message.getOriginatingAddress())) { 108 return processSms(message); 109 } 110 } 111 } 112 return false; 113 } 114 115 /** 116 * Get the Epoch time. 117 * 118 * @param year Year in string format. If this param is null or empty, a guessed year will be 119 * used. Some carriers do not provide this information in the SMS. 120 * @param month Month in string format. 121 * @param day Day in string format. 122 * @param hour Hour in string format. 123 * @param minute Minute in string format. 124 * @return The Epoch time in milliseconds. 125 */ getEpochTime(String year, String month, String day, String hour, String minute)126 private long getEpochTime(String year, String month, String day, String hour, String minute) { 127 LocalDateTime now = LocalDateTime.now(); 128 if (TextUtils.isEmpty(year)) { 129 // If year is not provided, guess the year from current time. 130 year = Integer.toString(now.getYear()); 131 } 132 133 LocalDateTime time; 134 // Check if the guessed year is reasonable. If it's the future, then the year must be 135 // the previous year. For example, the missed call's month and day is 12/31, but current 136 // date is 1/1/2020, then the year of missed call must be 2019. 137 do { 138 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm"); 139 time = LocalDateTime.parse(year + month + day + hour + minute, formatter); 140 year = Integer.toString(Integer.parseInt(year) - 1); 141 } while (time.isAfter(now)); 142 143 Instant instant = time.atZone(ZoneId.systemDefault()).toInstant(); 144 return instant.toEpochMilli(); 145 } 146 147 /** 148 * Process the SMS message 149 * 150 * @param message SMS message 151 * 152 * @return {@code true} if the SMS message has been processed as a missed incoming call SMS. 153 */ processSms(@onNull SmsMessage message)154 private boolean processSms(@NonNull SmsMessage message) { 155 long missedCallTime = 0; 156 String callerId = null; 157 158 String[] smsPatterns = mCarrierConfig.getStringArray(CarrierConfigManager 159 .KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY); 160 if (smsPatterns == null || smsPatterns.length == 0) { 161 Rlog.w(TAG, "Missed incoming call SMS pattern is not configured!"); 162 return false; 163 } 164 165 for (String smsPattern : smsPatterns) { 166 Pattern pattern; 167 try { 168 pattern = Pattern.compile(smsPattern, Pattern.DOTALL | Pattern.UNIX_LINES); 169 } catch (PatternSyntaxException e) { 170 Rlog.w(TAG, "Configuration error. Unexpected missed incoming call sms " 171 + "pattern: " + smsPattern + ", e=" + e); 172 continue; 173 } 174 175 Matcher matcher = pattern.matcher(message.getMessageBody()); 176 String year = null, month = null, day = null, hour = null, minute = null; 177 if (matcher.find()) { 178 try { 179 month = matcher.group(SMS_MONTH_TAG); 180 day = matcher.group(SMS_DAY_TAG); 181 hour = matcher.group(SMS_HOUR_TAG); 182 minute = matcher.group(SMS_MINUTE_TAG); 183 if (VDBG) { 184 Rlog.v(TAG, "month=" + month + ", day=" + day + ", hour=" + hour 185 + ", minute=" + minute); 186 } 187 } catch (IllegalArgumentException e) { 188 if (VDBG) { 189 Rlog.v(TAG, "One of the critical date field is missing. Using the " 190 + "current time for missed incoming call."); 191 } 192 missedCallTime = System.currentTimeMillis(); 193 } 194 195 // Year is an optional field. 196 try { 197 year = matcher.group(SMS_YEAR_TAG); 198 } catch (IllegalArgumentException e) { 199 if (VDBG) Rlog.v(TAG, "Year is missing."); 200 } 201 202 try { 203 if (missedCallTime == 0) { 204 missedCallTime = getEpochTime(year, month, day, hour, minute); 205 if (missedCallTime == 0) { 206 Rlog.e(TAG, "Can't get the time. Use the current time."); 207 missedCallTime = System.currentTimeMillis(); 208 } 209 } 210 211 if (VDBG) Rlog.v(TAG, "missedCallTime=" + missedCallTime); 212 } catch (Exception e) { 213 Rlog.e(TAG, "Can't get the time for missed incoming call"); 214 } 215 216 try { 217 callerId = matcher.group(SMS_CALLER_ID_TAG); 218 if (VDBG) Rlog.v(TAG, "caller id=" + callerId); 219 } catch (IllegalArgumentException e) { 220 Rlog.d(TAG, "Caller id is not provided or can't be parsed."); 221 } 222 createMissedIncomingCallEvent(missedCallTime, callerId); 223 return true; 224 } 225 } 226 227 Rlog.d(TAG, "SMS did not match any missed incoming call SMS pattern."); 228 return false; 229 } 230 231 // Create phone account. The logic is copied from PhoneUtils.makePstnPhoneAccountHandle. makePstnPhoneAccountHandle(Phone phone)232 private static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 233 return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, 234 String.valueOf(phone.getFullIccSerialNumber())); 235 } 236 237 /** 238 * Create the missed incoming call through TelecomManager. 239 * 240 * @param missedCallTime the time of missed incoming call in. This is the EPOCH time in 241 * milliseconds. 242 * @param callerId The caller id of the missed incoming call. 243 */ createMissedIncomingCallEvent(long missedCallTime, @Nullable String callerId)244 private void createMissedIncomingCallEvent(long missedCallTime, @Nullable String callerId) { 245 TelecomManager tm = (TelecomManager) mPhone.getContext() 246 .getSystemService(Context.TELECOM_SERVICE); 247 248 if (tm != null) { 249 Bundle bundle = new Bundle(); 250 251 if (callerId != null) { 252 final Uri phoneUri = Uri.fromParts( 253 PhoneAccount.SCHEME_TEL, callerId, null); 254 bundle.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, phoneUri); 255 } 256 257 // Need to use the Epoch time instead of the elapsed time because it's possible 258 // the missed incoming call occurred before the phone boots up. 259 bundle.putLong(TelecomManager.EXTRA_CALL_CREATED_EPOCH_TIME_MILLIS, missedCallTime); 260 tm.addNewIncomingCall(makePstnPhoneAccountHandle(mPhone), bundle); 261 } 262 } 263 } 264